Namespaces
Variants

Function declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Объявление функции вводит имя функции и её тип. Определение функции связывает имя/тип функции с телом функции.

Содержание

Объявление функции

Объявления функций могут появляться в любой области видимости. Объявление функции в области видимости класса вводит функцию-член класса (если не используется спецификатор friend ), подробнее см. member functions и friend functions .

noptr-declarator ( parameter-list ) cv  (необязательно) ref   (необязательно) except  (необязательно) attr  (необязательно) (1)
noptr-declarator ( parameter-list ) cv  (необязательно) ref   (необязательно) except  (необязательно) attr  (необязательно)
-> trailing
(2) (начиная с C++11)

(см. Declarations для других форм синтаксиса declarator )

1) Синтаксис обычного объявления функции.
2) Объявление возвращаемого типа в конце. decl-specifier-seq в этом случае должен содержать ключевое слово auto .
noptr-declarator - любой допустимый declarator , но если он начинается с * , & , или && , он должен быть заключен в круглые скобки.
parameter-list - возможно пустой, разделенный запятыми список параметров функции (подробности см. ниже)
attr - (since C++11) список attributes . Эти атрибуты применяются к типу функции, а не к самой функции. Атрибуты для функции появляются после идентификатора в деклараторе и объединяются с атрибутами, которые указаны в начале объявления, если таковые имеются.
cv - квалификация const/volatile, разрешена только в объявлениях нестатических функций-членов
ref - (since C++11) ref-квалификация, разрешена только в объявлениях нестатических функций-членов
except -

dynamic exception specification

(until C++11)

либо dynamic exception specification
или noexcept specification

(since C++11)
(until C++17)

noexcept specification

(since C++17)
trailing - Возвращаемый тип в конце, полезен если возвращаемый тип зависит от имен аргументов, как в template < class T, class U > auto add ( T t, U u ) - > decltype ( t + u ) ; или является сложным, как в auto fpif ( int ) - > int ( * ) ( int )


Как упоминалось в разделе Declarations , за декларатором может следовать requires clause , который объявляет связанные constraints для функции, которые должны быть удовлетворены для того, чтобы функция была выбрана в процессе overload resolution . (пример: void f1 ( int a ) requires true ; ) Обратите внимание, что связанное ограничение является частью сигнатуры функции, но не частью типа функции.

(since C++20)

Деклараторы функций могут комбинироваться с другими деклараторами, когда это допускает последовательность спецификаторов объявления :

// объявляет int, int*, функцию и указатель на функцию
int a = 1, *p = NULL, f(), (*pf)(double);
// decl-specifier-seq это int
// декларатор f() объявляет (но не определяет)
//                функцию без аргументов, возвращающую int
struct S
{
    virtual int f(char) const, g(int) &&; // объявляет две нестатические функции-члены
    virtual int f(char), x; // ошибка компиляции: virtual (в decl-specifier-seq)
                            // разрешено только в объявлениях нестатических
                            // функций-членов
};

Использование типа объекта с квалификатором volatile в качестве типа параметра или возвращаемого типа устарело.

(since C++20)

Возвращаемый тип функции не может быть типом функции или типом массива (но может быть указателем или ссылкой на них).

Как и в любом объявлении, атрибуты, появляющиеся перед объявлением, и атрибуты, появляющиеся непосредственно после идентификатора внутри декларатора, применяются к объявляемой или определяемой сущности (в данном случае, к функции):

[[noreturn]] void f [[noreturn]] (); // OK: both attributes apply to the function f

Однако атрибуты, появляющиеся после декларатора (в приведённом выше синтаксисе), применяются к типу функции, а не к самой функции:

void f() [[noreturn]]; // Error: this attribute has no effect on the function itself
(начиная с C++11)

Вывод типа возвращаемого значения

Если decl-specifier-seq объявления функции содержит ключевое слово auto , завершающий возвращаемый тип может быть опущен и будет выведен компилятором из типа операнда, используемого в неотброшенном return выражении. Если возвращаемый тип не использует decltype ( auto ) , вывод следует правилам template argument deduction :

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-выражением (включая операторы return без операнда), объявленный возвращаемый тип должен быть либо 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}; } // Ошибка

Виртуальные функции и корутины (since C++20) не могут использовать вывод типа возвращаемого значения:

struct F
{
    virtual auto f() { return 2; } // Ошибка
};

Шаблоны функций , за исключением пользовательских функций преобразования , могут использовать вывод типа возвращаемого значения. Вывод происходит при инстанциации, даже если выражение в return statement не является зависимым . Данное инстанцирование не находится в непосредственном контексте для целей 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)

Список параметров

Список параметров определяет аргументы, которые могут быть указаны при вызове функции. Это разделённый запятыми список объявлений параметров , каждое из которых имеет следующий синтаксис:

attr  (необязательно) decl-specifier-seq declarator (1)

attr  (необязательно) this decl-specifier-seq declarator

(2) (начиная с C++23)
attr  (необязательно) decl-specifier-seq declarator = initializer (3)
attr  (необязательно) decl-specifier-seq abstract-declarator  (необязательно) (4)

attr  (необязательно) this decl-specifier-seq abstract-declarator  (необязательно)

(5) (начиная с C++23)
attr  (необязательно) decl-specifier-seq abstract-declarator  (необязательно) = initializer (6)
void (7)
1) Объявляет именованный (формальный) параметр. Для значений decl-specifier-seq и declarator см. declarations .
int f ( int a, int * p, int ( * ( * x ) ( double ) ) [ 3 ] ) ;
2) Объявляет именованный явный объектный параметр .
3) Объявляет именованный (формальный) параметр со значением по умолчанию .
int f ( int a = 7 , int * p = nullptr, int ( * ( * x ) ( double ) ) [ 3 ] = nullptr ) ;
**Примечание:** Весь код C++ внутри тегов ` ` сохранен без изменений, как и требовалось. HTML-разметка и атрибуты также остались нетронутыми.
4) Объявляет безымянный параметр.
int f ( int , int * , int ( * ( * ) ( double ) ) [ 3 ] ) ;
5) Объявляет неименованный explicit object parameter .
6) Объявляет безымянный параметр со значением по умолчанию .
int f ( int = 7 , int * = nullptr, int ( * ( * ) ( double ) ) [ 3 ] = nullptr ) ;
7) Указывает, что функция не принимает параметров, это точный синоним пустого списка параметров: int f ( void ) ; и int f ( ) ; объявляют одну и ту же функцию.
void является единственным синтаксическим эквивалентом пустого списка параметров, другие использования void параметров являются некорректными:
Некорректное использование Пример
присутствуют несколько параметров int f1 ( void , int ) ;
параметр void имеет имя inf f2 ( void param ) ;
void имеет cv-квалификаторы int f3 ( const void ) ;
void является зависимым int f4 ( T ) ; (где T является void )
параметр void является явным объектным параметром (начиная с C++23) int f5 ( this void ) ;

Хотя decl-specifier-seq подразумевает, что могут существовать спецификаторы помимо спецификаторов типа, единственным другим допустимым спецификатором является register а также auto (до C++11) , и он не имеет эффекта.

(до C++17)

Если любой из параметров функции использует заполнитель (либо auto , либо тип концепта ), то объявление функции является объявлением сокращенного шаблона функции :

void f1(auto);    // same as template<class T> void f1(T)
void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept
(начиная с C++20)

Объявление параметра со спецификатором this (синтаксис ( 2 ) / ( 5 ) ) объявляет явный объектный параметр .

Явный объектный параметр не может быть пачкой параметров функции и может появляться только в качестве первого параметра в списке параметров в следующих объявлениях:

Функция-член с явным объектным параметром имеет следующие ограничения:

  • Функция не может быть static .
  • Функция не может быть virtual .
  • Декларатор функции не содержит cv и ref .
struct C
{
    void f(this C& self);     // OK
    template<typename Self>
    void g(this Self&& self); // also OK for templates
    void p(this C) const;     // Error: “const” not allowed here
    static void q(this C);    // Error: “static” not allowed here
    void r(int, this C);      // Error: an explicit object parameter
                              //        can only be the first parameter
};
// void func(this C& self);   // Error: non-member functions cannot have
                              //        an explicit object parameter
(начиная с C++23)

Имена параметров, объявленные в объявлениях функций, обычно служат только для самодокументирования. Они используются (но остаются необязательными) в определениях функций.

Неоднозначность возникает в списке параметров, когда имя типа заключено в круглые скобки (включая лямбда-выражения ) (начиная с C++11) . В этом случае выбор стоит между объявлением параметра типа указатель на функцию и объявлением параметра с избыточными скобками вокруг идентификатора декларатора . Разрешение заключается в том, чтобы рассматривать имя типа как простой спецификатор типа (который является типом указателя на функцию):

class C {};
void f(int(C)) {} // void f(int(*fp)(C param)) {}
                  // НЕ void f(int C) {}
void g(int *(C[10])); // void g(int *(*fp)(C param[10]));
                      // НЕ void g(int *C[10]);

Тип параметра не может быть типом, который включает ссылку или указатель на массив неизвестной границы, включая многоуровневые указатели/массивы таких типов, или указатель на функции, параметры которых являются такими типами.

Использование многоточия

Последний параметр в списке параметров может быть многоточием ( ... ); это объявляет вариативную функцию . Запятую перед многоточием можно опустить (устарело в C++26) :

int printf(const char* fmt, ...); // функция с переменным числом аргументов
int printf(const char* fmt...);   // то же самое, но устарело с C++26
template<typename... Args>
void f(Args..., ...); // шаблон функции с переменным числом аргументов и пакетом параметров
template<typename... Args>
void f(Args... ...);  // то же самое, но устарело с C++26
template<typename... Args>
void f(Args......);   // то же самое, но устарело с C++26

Тип функции

Список типов параметров

parameter-type-list функции определяется следующим образом:

  1. Тип каждого параметра (включая function parameter packs ) (since C++11) определяется из его собственного parameter declaration .
  2. После определения типа каждого параметра, любой параметр типа "array of T " или function type T преобразуется в "pointer to T ".
  3. После формирования списка типов параметров, любые top-level cv-qualifiers , модифицирующие тип параметра, удаляются при формировании function type.
  4. Полученный список преобразованных типов параметров и наличие или отсутствие ellipsis или function parameter pack (since C++11) является parameter-type-list функции.
void f(char*);         // #1
void f(char[]) {}      // определяет #1
void f(const char*) {} // OK, другая перегрузка
void f(char* const) {} // Ошибка: переопределяет #1
void g(char(*)[2]);   // #2
void g(char[3][2]) {} // определяет #2
void g(char[3][3]) {} // OK, другая перегрузка
void h(int x(const int)); // #3
void h(int (*)(int)) {}   // определяет #3

Определение типа функции

В синтаксисе (1) , предполагая noptr-declarator как самостоятельное объявление, при условии что тип qualified-id или unqualified-id в noptr-declarator равен «derived-declarator-type-list T »:

  • Если спецификация исключений является не выбрасывающей , тип объявленной функции -
    "derived-declarator-type-list noexcept function of
    parameter-type-list cv  (optional) ref   (optional) returning T ".
(начиная с C++17)
  • Тип (до C++17) В противном случае тип (начиная с C++17) объявленной функции является
    «список-производных-деклараторов-типов функция от
    списка-типов-параметров cv  (опционально) ref   (опционально) (начиная с C++11) возвращающая T ».

В синтаксисе (2) , предполагая noptr-declarator как самостоятельное объявление, при условии, что тип qualified-id или unqualified-id в noptr-declarator равен "derived-declarator-type-list T " ( T должен быть auto в этом случае):

(since C++11)
  • Если спецификация исключений является non-throwing , тип объявленной функции:
    "derived-declarator-type-list noexcept function of
    parameter-type-list cv  (optional) ref   (optional) returning trailing ".
(since C++17)
  • The (until C++17) Otherwise, the (since C++17) тип объявленной функции:
    "derived-declarator-type-list function of
    parameter-type-list cv  (optional) ref   (optional) returning trailing ".

attr , если присутствует, применяется к типу функции.

(since C++11)
// тип "f1" —
// "функция, принимающая int и возвращающая void, с атрибутом noreturn"
void f1(int a) [[noreturn]];
// тип "f2" —
// "constexpr noexcept функция, принимающая указатель на int и возвращающая int"
constexpr auto f2(int[] b) noexcept -> int;
struct X
{
    // тип "f3" —
    // "функция без параметров, константная, возвращающая const int"
    const int f3() const;
};

Квалификаторы в конце объявления

Тип функции с cv  или ref   (начиная с C++11) (включая тип, именованный через typedef ) может появляться только как:

typedef int FIC(int) const;
FIC f;     // Ошибка: не объявляет функцию-член
struct S
{
    FIC f; // OK
};
FIC S::*pm = &S::f; // OK

Сигнатура функции

Каждая функция имеет сигнатуру.

Сигнатура функции состоит из её имени и parameter-type-list . Её сигнатура также включает охватывающее namespace , за следующими исключениями:

  • Если функция является member function , её сигнатура содержит класс, членом которого является функция, вместо включающего пространства имён. Сигнатура также содержит следующие компоненты, если они существуют:
  • cv
  • ref
(начиная с C++11)
  • завершающая requires клауза
  • Если функция является нетemplate friend функцией с завершающей requires клаузой, её сигнатура содержит включающий класс вместо включающего пространства имён. Сигнатура также содержит завершающую requires клаузу.
(начиная с C++20)

except and attr (since C++11) не затрагивает сигнатуру функции , хотя noexcept спецификация влияет на тип функции (since C++17) .

Определение функции

Определение функции, не являющейся членом, может появляться только в области видимости пространства имен (вложенные функции отсутствуют). Определение функции-члена также может появляться в теле определения класса . Они имеют следующий синтаксис:

attr  (необязательно) decl-specifier-seq  (необязательно) declarator
virt-specs  (необязательно) contract-specs  (необязательно) function-body
(1)
attr  (необязательно) decl-specifier-seq  (необязательно) declarator
requires-clause contract-specs  (необязательно) function-body
(2) (since C++20)
1) Определение функции без ограничений.
2) Определение функции с ограничениями.
attr - (since C++11) список атрибутов . Эти атрибуты объединяются с атрибутами после идентификатора в деклараторе (см. начало страницы), если таковые имеются.
decl-specifier-seq - тип возвращаемого значения со спецификаторами, как в грамматике объявлений
declarator - функциональный декларатор, такой же как в грамматике объявления функций выше (может быть заключен в скобки)
virt-specs - (since C++11) override , final , или их комбинация в любом порядке
requires-clause - requires условие
contract-specs - (since C++26) список спецификаторов контракта функции
function-body - тело функции (см. ниже)


function-body может быть одним из следующих:

ctor-initializer  (необязательно) compound-statement (1)
function-try-block (2)
= default ; (3) (начиная с C++11)
= delete ; (4) (начиная с C++11)
= delete ( string-literal ); (5) (начиная с C++26)
1) Обычное тело функции.
3) Явно определенная по умолчанию функция.
4) Явно удалённое определение функции.
5) Явно удалённое определение функции с сообщением об ошибке.
ctor-initializer - список инициализации членов , разрешён только в конструкторах
compound-statement - заключённая в фигурные скобки последовательность операторов , составляющая тело функции
function-try-block - блок function try block
string-literal - невычисляемый строковый литерал , который может использоваться для объяснения причины удаления функции
int max(int a, int b, int c)
{
    int m = (a > b) ? a : b;
    return (m > c) ? m : c;
}
// decl-specifier-seq — это «int»
// declarator — это «max(int a, int b, int c)»
// body — это { ... }

Тело функции представляет собой составной оператор (последовательность из нуля или более операторов, заключенных в пару фигурных скобок), который выполняется при вызове функции. Кроме того, тело функции конструктора также включает следующее:

Если определение функции содержит virt-specs , оно должно определять функцию-член .

(начиная с C++11)

Если определение функции содержит requires-clause , оно должно определять шаблонную функцию .

(начиная с C++20)
void f() override {} // Ошибка: не функция-член
void g() requires (sizeof(int) == 4) {} // Ошибка: не шаблонная функция

Типы параметров, а также возвращаемый тип определения функции не могут быть (возможно cv-квалифицированными) неполными классами если только функция не определена как удалённая (since C++11) . Проверка полноты осуществляется только в теле функции, что позволяет функциям-членам возвращать класс, в котором они определены (или его внешний класс), даже если он неполон в точке определения (в теле функции он является полным).

Параметры, объявленные в деклараторе определения функции, находятся в области видимости внутри тела функции. Если параметр не используется в теле функции, его не нужно именовать (достаточно использовать абстрактный декларатор):

void print(int a, int) // второй параметр не используется
{
    std::printf("a = %d\n", a);
}

Несмотря на то, что топ-уровневые cv-квалификаторы параметров отбрасываются в объявлениях функций, они модифицируют тип параметра, видимый в теле функции:

void f(const int n) // объявляет функцию типа void(int)
{
    // но в теле тип "n" является const int
}

Функции по умолчанию

Если определение функции имеет синтаксис ( 3 ) , функция определяется как явно стандартная .

Функция, которая явно объявлена как default, должна быть специальной функцией-членом или функцией оператора сравнения (начиная с C++20) , и она не должна иметь аргументов по умолчанию .

Явно определенная по умолчанию специальная функция-член F1 может отличаться от соответствующей специальной функции-члена F2 , которая была бы неявно объявлена, следующим образом:

  • F1 и F2 могут иметь различные ref и/или except .
  • Если F2 имеет необъектный параметр типа const C & , соответствующий необъектный параметр F1 может иметь тип C& .
  • Если F2 имеет неявный параметр объекта типа "ссылка на C ", F1 может быть функцией-членом с явным объектным параметром, чей явный объектный параметр имеет (возможно, другой) тип "ссылка на C ", в этом случае тип F1 будет отличаться от типа F2 тем, что тип F1 имеет дополнительный параметр.
(начиная с C++23)

Если тип F1 отличается от типа F2 способом, отличным от разрешенного предыдущими правилами, тогда:

  • Если F1 является оператором присваивания, и возвращаемый тип F1 отличается от возвращаемого типа F2 или тип необъектного параметра F1 не является ссылкой, программа является некорректной.
  • Иначе, если F1 явно объявлен как default при первом объявлении, он определяется как deleted.
  • Иначе программа является некорректной.

Функция, явно заданная по умолчанию при первом объявлении, неявно является inline и неявно constexpr, если она может быть constexpr function .

struct S
{
    S(int a = 0) = default;             // ошибка: аргумент по умолчанию
    void operator=(const S&) = default; // ошибка: несоответствующий тип возвращаемого значения
    ~S() noexcept(false) = default;     // OK, другая спецификация исключений
private:
    int i;
    S(S&);          // OK, приватный конструктор копирования
};
S::S(S&) = default; // OK, определяет конструктор копирования

Явно-определенные по умолчанию функции и неявно объявленные функции вместе называются defaulted функциями. Их фактические определения будут предоставлены неявно, подробности смотрите на соответствующих страницах.

Удалённые функции

Если определение функции имеет синтаксис ( 4 ) или ( 5 ) (начиная с C++26) , функция определяется как явно удалённая .

Любое использование удалённой функции является некорректным (программа не скомпилируется). Это включает вызовы — как явные (с оператором вызова функции), так и неявные (вызов удалённого перегруженного оператора, специальной функции-члена, функции выделения памяти и т.д.), создание указателя или указателя на член на удалённую функцию, и даже использование удалённой функции в выражении, которое не является потенциально вычисляемым .

Нечистая виртуальная функция-член может быть определена как удалённая (deleted), даже если она неявно odr-used . Удалённая функция может быть переопределена только удалёнными функциями, а неудалённая функция может быть переопределена только неудалёнными функциями.

Если string-literal присутствует, реализация рекомендуется включать его текст как часть результирующего диагностического сообщения, которое показывает обоснование удаления или предлагает альтернативу.

(since C++26)

Если функция перегружена, разрешение перегрузки происходит сначала, и программа является некорректной только если удалённая функция была выбрана:

struct T
{
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete("new[] is deleted"); // since C++26
};
T* p = new T;    // Ошибка: попытка вызова удалённого T::operator new
T* p = new T[5]; // Ошибка: попытка вызова удалённого T::operator new[],
                 //        выводит диагностическое сообщение "new[] is deleted"

Удалённое определение функции должно быть первым объявлением в единице трансляции: ранее объявленная функция не может быть переобъявлена как удалённая:

struct T { T(); };
T::T() = delete; // Ошибка: должно быть удалено при первом объявлении

Пользовательские функции

Функция является предоставленной пользователем если она объявлена пользователем и не является явно defaulted или deleted при первом объявлении. Предоставленная пользователем явно defaulted функция (т.е. явно defaulted после первого объявления) определяется в точке, где она явно defaulted; если такая функция неявно определяется как deleted, программа является некорректной. Объявление функции как defaulted после первого объявления может обеспечить эффективное выполнение и краткое определение, одновременно позволяя стабильный бинарный интерфейс для развивающейся кодовой базы.

// Все специальные функции-члены «trivial»
// определены по умолчанию в своих первых объявлениях соответственно,
// они не являются предоставленными пользователем
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;

Разрешение Неоднозначностей

В случае неоднозначности между телом функции и инициализатором , начинающимся с { или = (since C++26) , неоднозначность разрешается проверкой типа деклараторного идентификатора для noptr-declarator :

  • Если тип является функциональным типом, неоднозначная последовательность токенов трактуется как тело функции.
  • В противном случае неоднозначная последовательность токенов трактуется как инициализатор.
using T = void(); // тип функции
using U = int;    // не функциональный тип
T a{}; // определяет функцию, не выполняющую действий
U b{}; // инициализирует значение объекта int
T c = delete("hello"); // определяет функцию как удаленную
U d = delete("hello"); // копирует инициализацию объекта int с
                       // результатом выражения delete (некорректно)

__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)

Спецификаторы контракта функции

Объявления функций и лямбда-выражения могут содержать последовательность спецификаторов контракта функции  , каждый спецификатор имеет следующий синтаксис:

pre attr  (необязательно) ( predicate ) (1)
post attr  (необязательно) ( predicate ) (2)
post attr  (необязательно) ( identifier result-attr  (необязательно) : predicate ) (3)
1) Вводит утверждение предусловия  .
2,3) Вводит постусловие утверждения  .
2) Утверждение не связывается с результатом.
3) Утверждение связывается с результатом.
attr - список атрибутов, относящихся к введенному контрактному утверждению
predicate - любое выражение (кроме непомещенных в скобки comma expressions )
identifier - идентификатор, который ссылается на результат
result-attr - список атрибутов, относящихся к привязке результата


Утверждение предусловия и утверждение постусловия вместе называются утверждением контракта функции  .

Утверждение контракта функции — это утверждение контракта , связанное с функцией. Предикат утверждения контракта функции представляет собой его предикат контекстно преобразованный в bool .

Следующие функции не могут быть объявлены с спецификаторами контракта функции:

Утверждения предусловий

Утверждение предусловия связано с входом в функцию:

int divide(int dividend, int divisor) pre(divisor != 0)
{
    return dividend / divisor;
}
double square_root(double num) pre(num >= 0)
{
    return std::sqrt(num);
}

Постусловия утверждений

Постусловие ассерта связано с нормальным выходом из функции.

Если постусловие содержит identifier , спецификатор контракта функции вводит identifier как имя result binding для связанной функции. Result binding обозначает объект или ссылку, возвращаемые вызовом этой функции. Тип result binding соответствует возвращаемому типу связанной функции.

int absolute_value(int num) post(r : r >= 0)
{
    return std::abs(num);
}
double sine(double num) post(r : r >= -1.0 && r <= 1.0)
{
    if (std::isnan(num) || std::isinf(num))
        // выход через исключение никогда не вызывает нарушение контракта
        throw std::invalid_argument("Неверный аргумент");
    return std::sin(num);
}

Если постусловие содержит идентификатор , а возвращаемый тип связанной функции является (возможно, cv-квалифицированным) void , программа является некорректно сформированной:

void f() post(r : r > 0); // Ошибка: для "r" не может быть связано значение

Когда объявленный возвращаемый тип нетemplated функции содержит тип-заполнитель , постусловие с идентификатором  может появляться только в определении функции:

auto g(auto&) post(r : r >= 0); // OK, "g" является шаблоном
auto h() post(r : r >= 0);      // Ошибка: невозможно указать имя возвращаемого значения
auto k() post(r : r >= 0)       // OK, "k" является определением
{
    return 0;
}

Согласованность контрактов

Переобъявление redeclaration D функции или шаблона функции func должно либо не иметь contract-specs , либо иметь те же contract-specs , что и любое первое объявление F , достижимое из D . Если D и F находятся в разных единицах трансляции, диагностика требуется только если D прикреплено к именованному модулю.

Если объявление F1 является первым объявлением func в одной единице трансляции, а объявление F2 является первым объявлением func в другой единице трансляции, F1 и F2 должны указывать одинаковые contract-specs , диагностика не требуется.

Два contract-specs  одинаковы, если они состоят из одинаковых спецификаторов контрактов функций в одинаковом порядке.

Спецификатор контракта функции C1 в объявлении функции D1 совпадает со спецификатором контракта функции C2 в объявлении функции D2 при выполнении всех следующих условий:

  • Предикаты predicate  C1 и C2 удовлетворяли бы правилу одного определения при размещении в определениях функций на объявлениях D1 и D2 (если D1 и D2 находятся в разных единицах трансляции, соответствующие сущности, определённые внутри каждого predicate ведут себя как единая сущность с единственным определением), соответственно, за исключением следующих переименований:
    • Переименование параметров объявленной функции.
    • Переименование параметров шаблона, охватывающего объявленную функцию.
    • Переименование привязки результата (если имеется).
  • Оба C1 и C2 имеют identifier или оба не имеют.

Если это условие не выполняется исключительно из-за сравнения двух лямбда-выражений, которые содержатся в predicate s, диагностика не требуется.

bool b1, b2;
void f() pre (b1) pre([]{ return b2; }());
void f(); // OK, спецификаторы контракта функции опущены
void f() pre (b1) pre([]{ return b2; }()); // Ошибка: замыкания имеют разные типы
void f() pre (b1); // Ошибка: спецификаторы контракта функции различаются
int g() post(r : b1);
int g() post(b1); // Ошибка: отсутствует привязка результата
namespace N
{
    void h() pre (b1);
    bool b1;
    void h() pre (b1); // Ошибка: спецификаторы контракта функции различаются
                       //        согласно правилу одного определения
}
(начиная с C++26)

Примечания

В случае неоднозначности между объявлением переменной с использованием синтаксиса прямой инициализации и объявлением функции, компилятор всегда выбирает объявление функции; см. direct-initialization .

Макрос тестирования возможностей Значение Стандарт Возможность
__cpp_decltype_auto 201304L (C++14) decltype(auto)
__cpp_return_type_deduction 201304L (C++14) выведение возвращаемого типа для обычных функций
__cpp_explicit_this_parameter 202110L (C++23) явные параметры объекта ( выведение this )
__cpp_deleted_function 202403L (C++26) удалённая функция с причиной

Ключевые слова

default , delete , pre , post

Пример

#include <iostream>
#include <string>
// простая функция с аргументом по умолчанию, не возвращающая значение
void f0(const std::string& arg = "world!")
{
    std::cout << "Hello, " << 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()("test!");
    fp11()("again!");
    int f2(std::string) noexcept; // объявление в области видимости функции
    std::cout << "f2(\"bad\"): " << f2("bad") << '\n';
    std::cout << "f2(\"42\"): " << f2("42") << '\n';
}
// простая нечленная функция, возвращающая int
int f1()
{
    return 007;
}
// функция с спецификацией исключений и блоком try функции
int f2(std::string str) noexcept
try
{
    return std::stoi(str);
}
catch (const std::exception& e)
{
    std::cerr << "stoi() failed!\n";
    return 0;
}
// удаленная функция, попытка вызова приводит к ошибке компиляции
void bar() = delete
#   if __cpp_deleted_function
    ("reason")
#   endif
;

Возможный вывод:

stoi() failed!
Hello, world!
Hello, test!
Hello, again!
f2("bad"): 0
f2("42"): 42

Отчёты о дефектах

Следующие отчеты об изменениях в поведении, содержащие описания дефектов, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применяется к Поведение в опубликованной версии Корректное поведение
CWG 135 C++98 функции-члены, определенные в классе,
не могли иметь параметр или возвращать
собственный класс, так как он неполный
разрешено
CWG 332 C++98 параметр мог иметь cv-квалифицированный void тип запрещено
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 1824 C++98 проверка полноты типа параметра и
возвращаемого типа определения функции могла выполняться
вне контекста определения функции
проверка только в
контексте
определения функции
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 2144 C++11 { } могло быть телом функции или инициализатором в одном месте различается по типу
идентификатора декларатора
CWG 2145 C++98 декларатор в определении функции не мог быть заключен в скобки разрешено
CWG 2259 C++11 правило разрешения неоднозначности относительно заключенных в скобки
имен типов не охватывало лямбда-выражения
охвачено
CWG 2430 C++98 в определении функции-члена в определении класса
тип этого класса не мог быть возвращаемым типом или
типом параметра из-за решения CWG issue 1824
проверка только в
теле функции
CWG 2760 C++98 тело функции конструктора не включало инициализации
не указанные в обычном теле функции конструктора
также включает эти
инициализации
CWG 2831 C++20 определение функции с requires-предложением
могло определять нешаблонную функцию
запрещено
CWG 2846 C++23 функции-члены с явным объектом не могли иметь определений вне класса разрешено
CWG 2915 C++23 неименованные параметры явного объекта могли иметь тип void запрещено

Смотрите также

C documentation для Declaring functions