Spec-Zone.ru › C++

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

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

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

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

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

(другие формы синтаксиса declarator см. в статье Объявления)

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

Динамическая спецификация исключений

(до C++11)

либо динамическая спецификация исключений
либо спецификация noexcept

(начиная с C++11)
(до C++17)

Спецификация noexcept

(начиная с C++17)
trailing - Завершающий тип возврата, полезный, если тип возврата зависит от имён аргументов, например 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)

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

// declares an int, an int*, a function, and a pointer to a function
int a = 1, *p = NULL, f(), (*pf)(double);
// decl-specifier-seq is int
// declarator f() declares (but doesn't define)
//                a function taking no arguments and returning int

struct S
{
    virtual int f(char) const, g(int) &&; // declares two non-static member functions
    virtual int f(char), x; // compile-time error: virtual (in decl-specifier-seq)
                            // is only allowed in declarations of non-static
                            // member functions
};

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

(начиная с 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), выведение выполняется по правилам выведения аргументов шаблона:

int x = 1;
auto f() { return x; }        // return type is int
const auto& f() { return x; } // return type is const int&

Если тип возврата — decltype(auto), тип возврата совпадает с типом, который был бы получен, если бы операнд, используемый в операторе return, был заключён в decltype:

int x = 1;
decltype(auto) f() { return x; }  // return type is int, same as decltype(x)
decltype(auto) f() { return(x); } // return type is int&, same as decltype((x))

(примечание: «constdecltype(auto)&» — ошибка; decltype(auto) необходимо использовать отдельно)

Если операторов return несколько, все они должны выводить один и тот же тип:

auto f(bool val)
{
    if (val) return 123; // deduces return type int
    else return 3.14f;   // Error: deduces return type float
}

Если оператор return отсутствует или его операнд представляет собой выражение типа void, объявленный тип возврата должен быть либо decltype(auto), и тогда выведенный тип возврата — void, либо auto (возможно, с cv-квалификаторами), и тогда выведенный тип возврата — void с теми же cv-квалификаторами:

auto f() {}              // returns void
auto g() { return f(); } // returns void
auto* x() {}             // Error: cannot deduce auto* from void

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

auto sum(int i)
{
    if (i == 1)
        return i;              // sum’s return type is int
    else
        return sum(i - 1) + i; // OK: sum’s return type is already known
}

Если в операторе return используется список инициализации в фигурных скобках, выведение не допускается:

auto func() { return {1, 2, 3}; } // Error

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

struct F
{
    virtual auto f() { return 2; } // Error
};

Шаблоны функций, за исключением пользовательских функций преобразования, могут использовать выведение типа возврата. Выведение выполняется при инстанцировании, даже если выражение в операторе return не является зависимым. Для целей SFINAE это инстанцирование не является непосредственным контекстом.

template<class T>
auto f(T t) { return t; }
typedef decltype(f(1)) fint_t;    // instantiates f<int> to deduce return type

template<class T>
auto f(T* t) { return *t; }
void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types,
                                  // chooses second template overload

Повторные объявления или специализации функций либо шаблонов функций, в которых используется выведение типа возврата, должны использовать те же заполнители типа возврата:

auto f(int num) { return num; }
// int f(int num);            // Error: no placeholder return type
// decltype(auto) f(int num); // Error: different placeholder

template<typename T>
auto g(T t) { return t; }
template auto g(int);     // OK: return type is int
// template char g(char); // Error: not a specialization of the primary template g

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

int f(int num);
// auto f(int num) { return num; } // Error: not a redeclaration of f

template<typename T>
T g(T t) { return t; }
template int g(int);      // OK: specialize T as int
// template auto g(char); // Error: not a specialization of the primary template g

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

template<typename T>
auto f(T t) { return t; }
extern template auto f(int); // does not instantiate f<int>

int (*p)(int) = f; // instantiates f<int> to determine its return type,
                   // but an explicit instantiation definition 
                   // is still required somewhere in the program
(начиная с 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 см. в статье Объявления.
intf(inta,int*p,int(*(*x)(double))[3]);
2) Объявляет именованный явный параметр объекта.
3) Объявляет именованный (формальный) параметр со значением по умолчанию.
intf(inta=7,int*p=nullptr,int(*(*x)(double))[3]=nullptr);
4) Объявляет безымянный параметр.
intf(int,int*,int(*(*)(double))[3]);
5) Объявляет безымянный явный параметр объекта.
6) Объявляет безымянный параметр со значением по умолчанию.
intf(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)) объявляет явный параметр объекта.

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

  • объявление функции-члена или шаблона функции-члена
  • явное инстанцирование или явная специализация шаблонизированной функции-члена
  • объявление лямбда-выражения

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

  • Функция не является статической.
  • Функция не является виртуальной.
  • Декларатор функции не содержит 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). В этом случае возможны два варианта: объявление параметра типа «указатель на функцию» или объявление параметра с избыточными скобками вокруг идентификатора declarator. Для разрешения неоднозначности имя типа считается простым спецификатором типа (то есть типом «указатель на функцию»):

class C {};

void f(int(C)) {} // void f(int(*fp)(C param)) {}
                  // NOT void f(int C) {}

void g(int *(C[10])); // void g(int *(*fp)(C param[10]));
                      // NOT void g(int *C[10]);

Тип нельзя определять в объявлении параметра функции или в типе возврата:

void f(struct S1 {} s); // Error
struct S2 {} g();       // Error

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

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

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

int printf(const char* fmt, ...); // a variadic function
int printf(const char* fmt...);   // same as above, but deprecated since C++26

template<typename... Args>
void f(Args..., ...); // a variadic function template with a parameter pack

template<typename... Args>
void f(Args... ...);  // same as above, but deprecated since C++26

template<typename... Args>
void f(Args......);   // same as above, but deprecated since C++26

Тип функции

Тип функции образуется с учётом наличия или отсутствия noexcept (присутствует тогда и только тогда, когда except не допускает исключений), (начиная с C++17)ref, (начиная с C++11)cv, списка типов параметров (см. ниже) и типа возврата.

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

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

  1. Тип каждого параметра (включая пакеты параметров функции)(начиная с C++11) определяется его собственным объявлением параметра.
  2. После определения типа каждого параметра любой параметр типа «массив из T» или типа функции T преобразуется в «указатель на T».
  3. После формирования списка типов параметров все cv-квалификаторы верхнего уровня, изменяющие тип параметра, удаляются при формировании типа функции.
  4. Полученный список преобразованных типов параметров и наличие или отсутствие многоточия или пакета параметров функции(начиная с C++11) и есть список типов параметров функции.
void f(char*);         // #1
void f(char[]) {}      // defines #1
void f(const char*) {} // OK, another overload
void f(char* const) {} // Error: redefines #1

void g(char(*)[2]);   // #2
void g(char[3][2]) {} // defines #2
void g(char[3][3]) {} // OK, another overload

void h(int x(const int)); // #3
void h(int (*)(int)) {}   // defines #3

Завершающие квалификаторы

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

  • тип функции для нестатической функции-члена,
  • тип функции, на которую указывает указатель на член,
  • тип функции верхнего уровня в объявлении typedef функции или в объявлении псевдонима(начиная с C++11),
  • type-id в аргументе по умолчанию параметра типа шаблона,
  • type-id аргумента шаблона для параметра типа шаблона,
  • операнд оператора рефлексии.
(начиная с C++26)
typedef int FIC(int) const;
FIC f;     // Error: does not declare a member function

struct S
{
    FIC f; // OK
};

FIC S::*pm = &S::f; // OK

constexpr auto yeti = ^^void(int) const&; // OK (since C++26)

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

У каждой функции есть сигнатура.

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

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

except и attr(начиная с C++11) не входят в сигнатуру функции, хотя спецификация noexcept влияет на тип функции(начиная с 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) (начиная с C++20)
1) Определение функции без ограничений.
2) Определение функции с ограничениями.
attr - (начиная с C++11) список атрибутов. Эти атрибуты объединяются с атрибутами, если таковые имеются, указанными после идентификатора в declarator (см. начало этой страницы).
decl-specifier-seq - тип возврата со спецификаторами, как в грамматике объявлений
declarator - декларатор функции, такой же, как в приведённой выше грамматике объявления функции (может быть заключён в скобки)
virt-specs - (начиная с C++11) override, final или их сочетание в любом порядке
requires-clause - предложение requires
contract-specs - (начиная с 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) Тело обычной функции.
2) Блок try функции.
3) Явно заданное определение функции по умолчанию.
4) Явно удалённое определение функции.
5) Явно удалённое определение функции с сообщением об ошибке.
ctor-initializer - список инициализаторов членов, разрешённый только в конструкторах
compound-statement - заключённая в фигурные скобки последовательность операторов, составляющая тело функции
function-try-block - блок try функции
string-literal - невычисляемый строковый литерал, который можно использовать для пояснения причины удаления функции
int max(int a, int b, int c)
{
    int m = (a > b) ? a : b;
    return (m > c) ? m : c;
}

// decl-specifier-seq is “int”
// declarator is “max(int a, int b, int c)”
// body is { ... }

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

  • Для всех нестатических элементов данных, идентификаторы которых отсутствуют в списке инициализаторов членов конструктора, используются инициализаторы членов по умолчанию или(начиная с C++11) инициализация по умолчанию для инициализации соответствующих подобъектов-членов.
  • Для всех базовых классов, имена типов которых отсутствуют в списке инициализаторов членов конструктора, используется инициализация по умолчанию для инициализации соответствующих подобъектов базового класса.

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

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

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

(начиная с C++20)
void f() override {} // Error: not a member function

void g() requires (sizeof(int) == 4) {} // Error: not a templated function

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

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

void print(int a, int) // second parameter is not used
{
    std::printf("a = %d\n", a);
}

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

void f(const int n) // declares function of type void(int)
{
    // but in the body, the type of “n” is const int
}

Функции, заданные по умолчанию

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

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

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

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

Если тип F1 отличается от типа F2 иначе, чем это допускается приведёнными выше правилами, то:

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

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

struct S
{
    S(int a = 0) = default;             // error: default argument
    void operator=(const S&) = default; // error: non-matching return type
    ~S() noexcept(false) = default;     // OK, different exception specification
private:
    int i;
    S(S&);          // OK, private copy constructor
};

S::S(S&) = default; // OK, defines copy constructor

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

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

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

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

Нечисто виртуальную функцию-член можно определить как удалённую, даже если она неявно используется в смысле ODR. Удалённую функцию могут переопределять только удалённые функции, а не удалённую функцию — только не удалённые функции.

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

(начиная с 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;    // Error: attempts to call deleted T::operator new
T* p = new T[5]; // Error: attempts to call deleted T::operator new[],
                 //        emits a diagnostic message “new[] is deleted”

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

struct T { T(); };
T::T() = delete; // Error: must be deleted on the first declaration

Функции, предоставленные пользователем

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

// All special member functions of “trivial” are
// defaulted on their first declarations respectively,
// they are not user-provided
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(); // first declaration
};

// not defaulted on the first declaration,
// it is user-provided and is defined here
nontrivial::nontrivial() = default;

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

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

  • Если тип является типом функции, неоднозначная последовательность токенов рассматривается как тело функции.
  • В противном случае неоднозначная последовательность токенов рассматривается как инициализатор.
using T = void(); // function type
using U = int;    // non-function type

T a{}; // defines a function doing nothing
U b{}; // value-initializes an int object

T c = delete("hello"); // defines a function as deleted
U d = delete("hello"); // copy-initializes an int object with
                       // the result of a delete expression (ill-formed)

__func__

В теле функции предопределённая переменная, локальная для функции, __func__ определяется так, как если бы она была объявлена следующим образом:

static const char __func__[] = "function-name";

Эта переменная имеет область видимости блока и статическую длительность хранения:

struct S
{
    S(): s(__func__) {} // OK: initializer-list is part of function body
    const char* s;
};
void f(const char* s = __func__); // Error: parameter-list is part of declarator
#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 - любое выражение (кроме непомещённых в скобки выражений с оператором запятая)
identifier - идентификатор, обозначающий результат
result-attr - список атрибутов, относящихся к привязке результата


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

Утверждение контракта функции — это утверждение контракта, связанное с функцией. Предикат утверждения контракта функции — это его predicate, контекстно преобразуемый к 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 как имя привязки результата связанной функции. Привязка результата обозначает объект или ссылку, возвращаемые при вызове этой функции. Тип привязки результата совпадает с возвращаемым типом связанной функции.

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))
        // exiting via an exception never causes contract violation
        throw std::invalid_argument("Invalid argument");
    return std::sin(num);
}

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

void f() post(r : r > 0); // Error: no value can be bound to “r”

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

auto g(auto&) post(r : r >= 0); // OK, “g” is a template

auto h() post(r : r >= 0);      // Error: cannot name the return value

auto k() post(r : r >= 0)       // OK, “k” is a definition
{
    return 0;
}

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

Повторное объявление 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 , диагностика не требуется.

bool b1, b2;

void f() pre (b1) pre([]{ return b2; }());
void f(); // OK, function contract specifiers omitted
void f() pre (b1) pre([]{ return b2; }()); // Error: closures have different types
void f() pre (b1); // Error: function contract specifiers are different

int g() post(r : b1);
int g() post(b1); // Error: no result binding

namespace N
{
    void h() pre (b1);
    bool b1;
    void h() pre (b1); // Error: function contract specifiers differ
                       //        according to the one−definition rule
}
(начиная с C++26)

Примечания

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

Макрос проверки поддержки возможности Значение Стандарт Возможность
__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>

// simple function with a default argument, returning nothing
void f0(const std::string& arg = "world!")
{
    std::cout << "Hello, " << arg << '\n';
}

// the declaration is in namespace (file) scope
// (the definition is provided later)
int f1();

// function returning a pointer to f0, pre-C++11 style
void (*fp03())(const std::string&)
{
    return f0;
}

// function returning a pointer to f0, with C++11 trailing return type
auto fp11() -> void(*)(const std::string&)
{
    return f0;
}

int main()
{
    f0();
    fp03()("test!");
    fp11()("again!");
    int f2(std::string) noexcept; // declaration in function scope
    std::cout << "f2(\"bad\"): " << f2("bad") << '\n';
    std::cout << "f2(\"42\"): " << f2("42") << '\n';
}

// simple non-member function returning int
int f1()
{
    return 007;
}

// function with an exception specification and a function try block
int f2(std::string str) noexcept
try
{
    return std::stoi(str);
}
catch (const std::exception& e)
{
    std::cerr << "stoi() failed!\n";
    return 0;
}

// deleted function, an attempt to call it results in a compilation error
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 declarator в определении функции нельзя было заключать в скобки разрешено
CWG 2259 C++11 правило разрешения неоднозначности для имён типов в скобках
не охватывало лямбда-выражения
охватывает
CWG 2430 C++98 при определении функции-члена в определении класса
тип этого класса не мог быть возвращаемым типом или
типом параметра из-за решения проблемы CWG 1824
проверка выполняется только
в теле функции
CWG 2760 C++98 тело функции конструктора не включало инициализации,
не указанные в обычном теле функции конструктора
также включает эти
инициализации
CWG 2831 C++20 определение функции с requires-clause
могло определять нешаблонную функцию
запрещено
CWG 2846 C++23 функции-члены с явным параметром объекта нельзя было определять вне класса разрешено
CWG 2915 C++23 неименованные явные параметры объекта могли иметь тип void запрещено

См. также

Документация по C о объявлении функций

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/function

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API