Объявление функции
Объявление функции вводит имя функции и её тип. Определение функции связывает имя/тип функции с телом функции.
Содержание |
[править] Объявление функции
Объявления функций могут появляться в любой области видимости. Объявление функции в области видимости класса вводит функцию-элемент класса (если не используется спецификатор friend), подробности смотрите в функции-элементы и дружественные функции.
Тип объявляемой функции состоит из типа возвращаемого значения (предоставленного последовательностью-спецификаторов-обявления синтаксиса объявления) и декларатора функции
декларатор-не-указатель ( список-параметров ) cv (необязательно) ссылка (необязательно) except (необязательно) атрибуты (необязательно)
|
(1) | ||||||||
декларатор-не-указатель ( список-параметров ) cv (необязательно) ссылка (необязательно) except (необязательно) атрибуты (необязательно)-> завершение
|
(2) | (начиная с C++11) | |||||||
(смотрите Объявления для других форм синтаксиса декларатора)
| декларатор-не-указатель | — | любой действительный декларатор, но если он начинается с *, & или &&, он должен быть заключён в круглые скобки.
| ||||||||
| список-параметров | — | возможно пустой список параметров функции, разделённых запятыми (подробности смотрите ниже) | ||||||||
| атрибуты | — | (начиная с C++11) список атрибутов. Эти атрибуты применяются к типу функции, а не к самой функции. Атрибуты для функции появляются после идентификатора в деклараторе и объединяются с атрибутами, которые появляются в начале объявления, если таковые имеются. | ||||||||
| cv | — | квалификация const/volatile, разрешена только в объявлениях нестатических функций-элемаентов | ||||||||
| ссылка | — | (начиная с C++11) ref-квалификация, разрешена только в объявлениях нестатических функций-элементов | ||||||||
| except | — |
| ||||||||
| завершение | — | Завершающий возвращаемый тип, полезен, если возвращаемый тип зависит от имен аргументов, таких как template<class T, class U> auto add(T t, U u) -> decltype(t + u); или как в auto fpif(int)->int(*)(int) |
|
Как упоминалось в Объявлениях, за декларатором может следовать предложение requires, которое объявляет связанные ограничения для функции, которые должны быть удовлетворены, чтобы функция была выбрана при разрешении перегрузки. (пример: void f1(int a) requires true;) Обратите внимание, что связанное ограничение является частью сигнатуры функции, но не частью типа функции. |
(начиная с C++20) |
Деклараторы функций можно смешивать с другими деклараторами, где позволяет последовательность-спецификаторов-объявления:
// объявляет int, int*, функцию и указатель на функцию int a = 1, *p = NULL, f(), (*pf)(double); // последовательность-идентификаторов-объявления это int // декларатор f() объявляет (но не определяет) // функцию, не принимающую аргументов и возвращающую целое число struct S { virtual int f(char) const, g(int) &&; // объявляет две нестатические функции-элементы virtual int f(char), x; // ошибка времени компиляции: virtual // (в последовательности-идентификаторов-объявления) // разрешён только в объявлениях нестатических // функций-элементов };
|
Использование volatile-квалифицированного типа объекта в качестве типа параметра или возвращаемого значения не рекомендуется. |
(начиная с C++20) |
Тип возвращаемого значения функции не может быть типом функции или типом массива (но может быть указателем или ссылкой на них).
|
Как и в случае любого объявления, атрибуты, которые появляются перед объявлением, и атрибуты, которые появляются сразу после идентификатора в деклараторе, применяются к объявляемой или определяемой сущности (в данном случае к функции): [[noreturn]] void f [[noreturn]] (); // ok: оба атрибута применяются к функции f Однако атрибуты, которые появляются после декларатора (в приведённом выше синтаксисе), относятся к типу функции, а не к самой функции: void f() [[noreturn]]; // ошибка: этот атрибут не влияет на саму функцию |
(начиная с C++11) |
Как и в случае любого объявления, тип функции func, объявленной как ret func(параметры), равен ret(параметры) (за исключением перезаписи типа параметра, описанной ниже): смотрите именование типов.
Вывод типа возвращаемого значенияЕсли последовательность-спецификаторов-объявления функции содержит ключевое слово auto, конечный возвращаемый тип может быть опущен и будет выведен компилятором из типа выражения, используемого в операторе return. Если возвращаемый тип не использует decltype(auto), вывод следует правилам вывода аргументов шаблона: int x = 1; auto f() { return x; } // тип возвращаемого значения int const auto& f() { return x; } // тип возвращаемого значения const int& Если возвращаемый тип это decltype(auto), возвращаемый тип будет таким, какой был бы получен, если бы выражение, используемое в операторе return, было заключено в decltype: int x = 1; decltype(auto) f() { return x; } // возвращаемый тип int, такой же, как decltype(x) decltype(auto) f() { return(x); } // возвращаемый тип int&, такой же, как decltype((x)) (примечание: "const decltype(auto)&" является ошибкой, decltype(auto) должен использоваться сам по себе) Если имеется несколько операторов return, все они должны приводить к одному и тому же типу: auto f(bool val) { if (val) return 123; // выводит возвращаемый тип int else return 3.14f; // ошибка: выводит возвращаемый тип float } Если оператора return нет или если аргументом оператора return является выражение void, объявленный тип возвращаемого значения должен быть либо decltype(auto), и в этом случае выведенный тип возвращаемого значения равен void или (возможно, cv-квалифицированный) auto, и в этом случае выведенный возвращаемый тип (идентичной cv-квалификации) void: auto f() {} // возвращает void auto g() { return f(); } // возвращает void auto* x() {} // ошибка: невозможно вывести auto* из void После того, как оператор return был встречен в функции, тип возвращаемого значения, выведенный из этого оператора, может использоваться в остальной части функции, в том числе в других операторах return: auto sum(int i) { if (i == 1) return i; // возвращаемый тип sum это int else return sum(i - 1) + i; // ok: тип возвращаемого значения sum уже известен } Если оператор return использует список-инициализации-в-фигурных скобках, вывод не допускается: auto func () { return {1, 2, 3}; } // ошибка Виртуальные функции и сопрограммы (начиная с C++20)не могут использовать вывод возвращаемого типа: struct F { virtual auto f() { return 2; } // ошибка }; Шаблоны функций, отличные от определяемых пользователем функций преобразования, могут использовать вывод типа возвращаемого значения. Вывод происходит при создании экземпляра, даже если выражение в операторе возврата не является зависимым. Это инстанцирование не находится в непосредственном контексте для целей SFINAE. template<class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; // создаёт экземпляр f<int> для вывода // возвращаемого типа template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // создаёт обе функции f для определения возвращаемых // типов, выбирает вторую перегрузку шаблона При повторном объявлении или специализации функций или шаблонов функций, использующих вывод возвращаемого типа, должны использоваться те же заполнители возвращаемого типа: auto f(int num) { return num; } // int f(int num); // ошибка: нет заполнителя возвращаемого типа // decltype(auto) f(int num); // ошибка: другой заполнитель template<typename T> auto g(T t) { return t; } template auto g(int); // ok: тип возвращаемого значения int // template char g(char); // ошибка: не специализация основного шаблона g Точно так же повторные объявления или специализации функций или шаблонов функций, которые не используют вывод типа возвращаемого значения, не должны использовать заполнитель: int f(int num); // auto f(int num) { return num; } // ошибка: не повторное объявление f template<typename T> T g(T t) { return t; } template int g(int); // ok: специализиция T как int // template auto g(char); // ошибка: не специализация основного шаблона g Явные объявления создания экземпляров сами по себе не создают экземпляры шаблонов функций, которые используют вывод возвращаемого типа: template<typename T> auto f(T t) { return t; } extern template auto f(int); // не создаёт экземпляр f<int> int (*p)(int) = f; // создаёт экземпляр f<int> для определения типа возвращаемого значения, // но где-то в программе по-прежнему требуется // явное определение создания экземпляра |
(начиная с C++14) |
[править] Список параметров
Список параметров определяет аргументы, которые могут быть указаны при вызове функции. Это разделённый запятыми список объявлений параметров, каждое из которых имеет следующий синтаксис:
| атрибуты (необязательно) последовательность-спецификаторов-объявления декларатор | (1) | ||||||||
атрибуты (необязательно) последовательность-спецификаторов-объявления декларатор = инициализатор
|
(2) | ||||||||
| атрибуты (необязательно) последовательность-спецификаторов-объявления абстрактный-декларатор (необязательно) | (3) | ||||||||
атрибуты (необязательно) последовательность-спецификаторов-объявления абстрактный-декларатор (необязательно) = инициализатор
|
(4) | ||||||||
void
|
(5) | ||||||||
int f(int a, int* p, int (*(*x)(double))[3]);
int f(int a = 7, int* p = nullptr, int (*(*x)(double))[3] = nullptr);
int f(int, int*, int (*(*)(double))[3]);
int f(int = 7, int* = nullptr, int (*(*)(double))[3] = nullptr);
T, не становится функцией без параметров, если она создаётся с помощью T = void).В конце списка параметров может появиться многоточие ...; это объявляет вариативную функцию:
int printf(const char* fmt ...);
Для совместимости с C89 перед многоточием может стоять необязательная запятая, если список параметров содержит хотя бы один параметр:
int printf(const char* fmt, ...); // OK, то же, что и выше
|
Хотя последовательность-спецификаторов-атрибутов подразумевает, что могут существовать спецификаторы, отличные от спецификаторов типа, единственным разрешённым спецификатором является register а также auto (до C++11), и он не имеет эффекта. |
(до C++17) |
|
Если какой-либо из параметров функции использует заполнитель (либо auto, либо тип концепта), объявление функции является сокращённым объявлением шаблона функции: void f1(auto); // то же, что и template<class T> void f1(T) void f2(C1 auto); // то же, что и template<C1 T> void f2(T), если C1 является концептом |
(начиная с C++20) |
Имена параметров, объявленные в объявлениях функций, обычно используются только в целях самодокументирования. Они используются (но остаются необязательными) в определениях функций
Тип каждого параметра функции в списке параметров определяется в соответствии со следующими правилами:
Из-за этих правил следующие объявления функций объявляют одну и ту же функцию:
int f(char s[3]); int f(char[]); int f(char* s); int f(char* const); int f(char* volatile s);
Следующие объявления также объявляют одну и ту же функцию:
int f(int()); int f(int (*g)());
Неоднозначность возникает в списке параметров, когда имя типа заключено в круглые скобки (включая лямбда-выражения) (начиная с C++11). В этом случае выбор делается между объявлением параметра типа указателя на функцию и объявлением параметра с избыточными круглыми скобками вокруг идентификатора декларатора. Решение состоит в том, чтобы рассматривать имя типа как простой спецификатор типа (который является указателем на тип функции):
class C {}; void f(int(C)) {} // void f(int(*fp)(C параметр)) {} // НЕ void f(int C) {} void g(int *(C[10])); // void g(int *(*fp)(C параметр[10])); // НЕ void g(int *C[10]);
Тип параметра не может быть типом, включающим ссылку или указатель на массив неизвестных границ, включая многоуровневые указатели/массивы таких типов, или указателем на функции, параметры которых являются такими типами.
|
Перед многоточием, указывающим на переменное число аргументов, не обязательно должна стоять запятая, даже если она следует за многоточием, указывающим на расширение пакета параметров, поэтому следующие шаблоны функций одинаковые: template<typename... Args> void f(Args..., ...); template<typename... Args> void f(Args... ...); template<typename... Args> void f(Args......); Примером использования такого объявления является возможная реализация функции std::is_function. Запустить этот код #include <cstdio> template<typename... Variadic, typename... Args> constexpr void invoke(auto (*fun)(Variadic......), Args... args) { fun(args...); } int main() { invoke(std::printf, "%dm•%dm•%dm = %d%s%c", 2,3,7, 2*3*7, "m³", '\n'); } Вывод: 2m•3m•7m = 42m³ |
(начиная с C++11) |
[править] Определение функции
Определение функции, не являющейся элементом, может появляться только в области пространства имён (не во вложенных функциях). Определение функции-элемента также может появиться в теле определения класса. Они имеют следующий синтаксис:
| атрибуты (необязательно) последовательность-спецификаторов-объявления (необязательно) декларатор последовательность-виртуальных-спецификаторов (необязательно) тело-фукнции | |||||||||
где тело-функции является одним из следующих
| инициализатор-конструктора (необязательно) составное-выражение | (1) | ||||||||
| блок-try-функции | (2) | ||||||||
= delete ;
|
(3) | (начиная с C++11) | |||||||
= default ;
|
(4) | (начиная с C++11) | |||||||
| атрибуты | — | (начиная с C++11) список атрибутов. Эти атрибуты объединяются с атрибутами после идентификатора в деклараторе (смотрите начало этой страницы), если таковые имеются. |
| последовательность-спецификаторов-объявления | — | возвращаемый тип со спецификаторами, как в грамматике объявлений |
| декларатор | — | декларатор функции, такой же, как в приведённой выше грамматике объявления функции (может быть заключён в скобки). как и в случае с объявлением функции, за ним может следовать предложение requires (начиная с C++20) |
| последовательность-виртуальных-спецификаторов | — | (начиная с C++11) override, final или их комбинация в любом порядке (разрешено только для нестатических функций-элементов)
|
| инициализатор-конструктора | — | список инициализаторов элементов, разрешён только в конструкторах |
| составное-выражение | — | заключённая в фигурные скобки последовательность операторов, составляющая тело функции |
int max(int a, int b, int c) { int m = (a > b) ? a : b; return (m > c) ? m : c; } // последовательность-спецификаторов-объявления это "int" // декларатор это "max(int a, int b, int c)" // тело это { ... }
Тело функции представляет собой составной оператор (последовательность из нуля или более операторов, заключённых в пару фигурных скобок), который выполняется при вызове функции.
Типы параметров, а также возвращаемый тип определения функции не могут быть неполными классовыми типами, если функция не определена как удалённая. (начиная с C++11). Полная проверка выполняется в контексте тела функции, что позволяет функциям-элементам возвращать класс, в котором они определены (или его объемлющий класс), даже если он неполный в точке определения (он завершён в теле функции).
Параметры, объявленные в деклараторе определения функции, находятся в области видимости тела. Если параметр не используется в теле функции, его не нужно именовать (достаточно использовать абстрактный декларатор):
void print(int a, int) // второй параметр не используется { std::printf("a = %d\n", a); }
Несмотря на то, что cv-квалификаторы верхнего уровня для параметров в объявлениях функций отбрасываются, они изменяют тип параметра, видимый в теле функции:
void f(const int n) // объявляет функцию типа void(int) { // но в теле n имеет тип const int }
Удалённые функцииЕсли вместо тела функции используется специальный синтаксис struct sometype { void* operator new(std::size_t) = delete; void* operator new[](std::size_t) = delete; }; sometype* p = new sometype; // ошибка: попытка вызова удалённого sometype::operator new Определение удаления функции должно быть первым объявлением в единице трансляции: ранее объявленная функция не может быть повторно объявлена как удалённая: struct sometype { sometype(); }; sometype::sometype() = delete; // ошибка: необходимо удалить при первом объявлении Пользовательские функцииФункция является предоставленной пользователем, если она объявлена пользователем и не установлена явным образом по умолчанию или не удалена при первом объявлении. Предоставленная пользователем функция явная по умолчанию (т.е. явно установленная по умолчанию после её первого объявления) определяется в точке, где она явно установлена по умолчанию; если такая функция неявно определена как удалённая, программа некорректна. Объявление функции по умолчанию после её первого объявления может обеспечить эффективное выполнение и краткое определение, обеспечивая при этом стабильный двоичный интерфейс для развивающейся базы кода. // Все специальные `тривиальные` функции-элементы // устанавливающиеся по умолчанию в их первых объявлениях, // не предоставляются пользователем struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial { nontrivial(); // первое объявление }; // не по умолчанию в первом объявлении, // она предоставляется пользователем и определяется здесь nontrivial::nontrivial() = default;
__func__Внутри тела функции предопределённая локальная переменная __func__ определяется как static const char __func__[] = "имя-функции"; Эта переменная имеет область видимости блока и статическую длительность хранения: struct S { S(): s(__func__) {} // ok: список инициализаторов является частью тела функции const char* s; }; void f(const char* s = __func__); // ошибка: список параметров является частью декларатора Запустить этот код Возможный вывод: Foo Bar Pub ~Bar |
(начиная с C++11) |
[править] Примечание
В случае неоднозначности между объявлением переменной с использованием синтаксиса прямой инициализации и объявлением функции компилятор всегда выбирает объявление функции; смотрите прямая инициализация.
| Макрос тест функциональности | Значение | Стандарт | Комментарий |
|---|---|---|---|
__cpp_decltype_auto |
201304L | (C++14) | decltype(auto)
|
__cpp_return_type_deduction |
201304L | (C++14) | Вывод типа возвращаемого значения для обычных функций |
[править] Пример
#include <iostream> #include <string> // простая функция с аргументом по умолчанию, ничего не возвращающая void f0(const std::string& arg = "мир!") { std::cout << "Привет, " << arg << '\n'; } // объявление находится в области видимости пространства имён (файла) // (определение будет позже) int f1(); // функция, возвращающая указатель на f0, стиль до C++11 void (*fp03())(const std::string&) { return f0; } // функция, возвращающая указатель на f0, с конечным возвращаемым типом C++11 auto fp11() -> void(*)(const std::string&) { return f0; } int main() { f0(); fp03()("тест!"); fp11()("снова!"); int f2(std::string) noexcept; // объявление в области видимости функции std::cout << "f2(\"плохо\"): " << f2("плохо") << '\n'; std::cout << "f2(\"42\"): " << f2("42") << '\n'; } // простая функция, не являющаяся элементом, возвращает целое число int f1() { return 007; } // функция со спецификацией исключения и блоком try функции int f2(std::string str) noexcept try { return std::stoi(str); } catch (const std::exception& e) { std::cerr << "сбой в stoi()!\n"; return 0; }
Возможный вывод:
сбой в stoi()!
Привет, мир!
Привет, тест!
Привет, снова!
f2("плохо"): 0
f2("42"): 42[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 135 | c++98 | функции-элементы, определённые в классе, не могут иметь параметром или возвращать собственный класс, потому что он неполный |
позволено |
| CWG 332 | C++98 | параметр может иметь cv-квалифицированный тип | запрещено |
| CWG 393 | c++98 | типы, которые включают указатели/ссылки на массив неизвестной границы, не могут быть параметрами |
такие типы разрешены |
| CWG 452 | C++98 | список инициализаторов элементов не был частью тела функции |
сделан частью тела функции, изменив синтаксис определения функции |
| CWG 577 | c++98 | зависимый тип void может использоваться для объявления функции без параметров |
допускается только независимый void |
| CWG 1327 | C++11 | функции по умолчанию или удалённые нельзя было специфицировать с помощью override или final |
разрешено |
| CWG 1355 | C++11 | только специальные функции-элементы могут быть предоставлены пользователем |
распространяется на все функции |
| CWG 1394 | C++11 | удалённые функции не могли иметь параметр неполного типа или возвращать неполный тип |
неполный тип допускается |
| CWG 1877 | C++14 | вывод возвращаемого типа обрабатывается return; как return void(); |
в этом случае просто выведется возвращаемый тип как void |
| CWG 2015 | C++11 | неявное использование odr удалённой виртуальной функции было неправильно |
такое использование odr освобождается от запрета на использование |
| CWG 2044 | C++14 | вывод типа возвращаемого значения для функций, возвращающих void, завершается ошибкой, если объявленный тип возвращаемого значения decltype(auto) |
обновлено правило вывода, чтобы обработать этот случай |
| CWG 2081 | C++14 | переобъявления функций могут использовать вывод типа возвращаемого значения, даже если первоначальное объявление не делала этого |
не разрешено |
| CWG 2145 | C++98 | декларатор в определении функции не может быть заключён в скобки |
разрешено |
| CWG 2259 | C++11 | правило разрешения неоднозначности в отношении имён типов в скобках не распространялось на лямбда-выражения |
распространяется |
| CWG 2430 | C++98 | в определении функции-элемента в определении класса тип этого класса не может быть типом возвращаемого значения или типом параметра из-за разрешения CWG проблема 1824 |
сделана проверка полноты только в теле функции |
[править] Смотрите также
| Документация C по Объявление функций
|

