Объявление функции
Объявление функции вводит имя функции и её тип. Определение функции связывает имя/тип функции с телом функции.
Объявление функции
Объявления функций могут появляться в любой области видимости. Объявление функции в области видимости класса вводит функцию-элемент класса (если не используется спецификатор friend), подробности смотрите в функции-элементы и дружественные функции.
Тип объявляемой функции состоит из типа возвращаемого значения (предоставленного последовательностью-спецификаторов-обявления синтаксиса объявления) и декларатора функции
декларатор-не-указатель ( список-параметров ) cv (необязательно) ссылка (необязательно) except (необязательно) атрибуты (необязательно)
|
(1) | ||||||||
декларатор-не-указатель ( список-параметров ) cv (необязательно) ссылка (необязательно) except (необязательно) атрибуты (необязательно)-> завершение
|
(2) | (начиная с C++11) | |||||||
(смотрите Объявления для других форм синтаксиса декларатора)
auto.| декларатор-не-указатель | — | любой действительный декларатор, но если он начинается с *, & или &&, он должен быть заключён в круглые скобки.
| ||||||||
| список-параметров | — | возможно пустой список параметров функции, разделённых запятыми (подробности смотрите ниже) | ||||||||
| атрибуты | — | (начиная с 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, которое объявляет связанные ограничения для функции, которые должны быть удовлетворены, чтобы функция была выбрана при разрешении перегрузки. (пример: |
(начиная с 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(параметры) (за исключением перезаписи типа параметра, описанной ниже): смотрите именование типов.
Вывод типа возвращаемого значенияЕсли последовательность-спецификаторов-объявления функции содержит ключевое слово int x = 1;
auto f() { return x; } // тип возвращаемого значения int
const auto& f() { return x; } // тип возвращаемого значения const int&
Если возвращаемый тип это int x = 1;
decltype(auto) f() { return x; } // возвращаемый тип int, такой же, как decltype(x)
decltype(auto) f() { return(x); } // возвращаемый тип int&, такой же, как decltype((x))
(примечание: " Если имеется несколько операторов return, все они должны приводить к одному и тому же типу: auto f(bool val)
{
if (val) return 123; // выводит возвращаемый тип int
else return 3.14f; // ошибка: выводит возвращаемый тип float
}
Если оператора return нет или если аргументом оператора return является выражение 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);
int f(void); и int f(); объявляют одну и ту же функцию. Обратите внимание, что тип void (возможно, cv-квалифицированный) не может использоваться в списке параметров иначе: int f(void, int); и int f(const void); являются ошибками (хотя можно использовать производные типы, такие как void*). В шаблоне можно использовать только независимый тип void (функция, принимающая один параметр типа T, не становится функцией без параметров, если она создаётся с помощью T = void).В конце списка параметров может появиться многоточие ...; это объявляет вариативную функцию:
int printf(const char* fmt ...);
Для совместимости с C89 перед многоточием может стоять необязательная запятая, если список параметров содержит хотя бы один параметр:
int printf(const char* fmt, ...); // OK, то же, что и выше
|
Хотя последовательность-спецификаторов-атрибутов подразумевает, что могут существовать спецификаторы, отличные от спецификаторов типа, единственным разрешённым спецификатором является |
(до C++17) |
|
Если какой-либо из параметров функции использует заполнитель (либо void f1(auto); // то же, что и template<class T> void f1(T)
void f2(C1 auto); // то же, что и template<C1 T> void f2(T), если C1 является концептом
|
(начиная с C++20) |
Имена параметров, объявленные в объявлениях функций, обычно используются только в целях самодокументирования. Они используются (но остаются необязательными) в определениях функций
Тип каждого параметра функции в списке параметров определяется в соответствии со следующими правилами:
int f(const int p, decltype(p)*); и int f(int, const int*); объявляют одну и ту же функцию) .Из-за этих правил следующие объявления функций объявляют одну и ту же функцию:
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__); // ошибка: список параметров является частью декларатора
Запустить этот код #include <iostream>
void Foo() { std::cout << __func__ << ' '; }
struct Bar
{
Bar() { std::cout << __func__ << ' '; }
~Bar() { std::cout << __func__ << ' '; }
struct Pub { Pub() { std::cout << __func__ << ' '; } };
};
int main()
{
Foo();
Bar bar;
Bar::Pub pub;
}
Возможный вывод: 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 по Объявление функций
|