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

(有关 declarator 语法的其他形式,请参阅 Declarations

1) 常规函数声明符语法。
2) 后置返回类型声明。此种情况下 decl-specifier-seq 必须包含关键字 auto
noptr-declarator - 任意有效的 declarator ,但如果以 * & && 开头,则必须用括号括起来
parameter-list - 可能为空的函数参数逗号分隔列表(详见下文)
attr - (C++11 起) 属性 列表。这些属性应用于函数类型而非函数本身。函数的属性出现在声明符内的标识符之后,并与声明开头出现的属性(如果有)合并
cv - const/volatile 限定符,仅允许在非静态成员函数声明中使用
ref - (C++11 起) 引用限定符,仅允许在非静态成员函数声明中使用
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)

函数声明符可以与其他声明符混合使用,只要 声明说明符序列 允许:

// 声明一个int、一个int指针、一个函数和一个函数指针
int a = 1, *p = NULL, f(), (*pf)(double);
// 声明说明符序列是int
// 声明符f()声明(但不定义)
//                一个不接受参数且返回int的函数
struct S
{
    virtual int f(char) const, g(int) &&; // 声明两个非静态成员函数
    virtual int f(char), x; // 编译时错误:virtual(在声明说明符序列中)
                            // 仅允许在非静态成员函数的声明中使用
};

将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
(since C++11)

返回类型推导

如果函数声明的 decl-specifier-seq 包含关键字 auto ,则可以省略尾随返回类型,编译器将从 非丢弃 return 语句中使用的操作数类型推导得出。如果返回类型未使用 decltype ( auto ) ,则推导遵循 模板实参推导 规则:

int x = 1;
auto f() { return x; }        // 返回类型为 int
const auto& f() { return x; } // 返回类型为 const int&

如果返回类型是 decltype ( auto ) ,则返回类型等同于将返回语句中使用的操作数包裹在 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 ) 必须单独使用)

如果存在多个返回语句,它们必须全部推导为相同类型:

auto f(bool val)
{
    if (val) return 123; // 推导返回类型为 int
    else return 3.14f;   // 错误:推导返回类型为 float
}

如果没有返回语句,或者返回语句的操作数是 void 表达式(包括无操作数的返回语句),则声明的返回类型必须是 decltype ( auto ) (此时推导的返回类型为 void ),或者是(可能带有 cv 限定符的) auto (此时推导的返回类型为(具有相同 cv 限定符的) void ):

auto f() {}              // 返回 void
auto g() { return f(); } // 返回 void
auto* x() {}             // 错误:无法从 void 推导出 auto*

一旦在函数中遇到返回语句,从该语句推导出的返回类型便可用于函数的其余部分,包括其他返回语句:

auto sum(int i)
{
    if (i == 1)
        return i;              // sum的返回类型为int
    else
        return sum(i - 1) + i; // 正确: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);     // 正确:返回类型为 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);      // 正确:将 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 ) ;
4) 声明一个未命名参数。
int f ( int , int * , int ( * ( * ) ( double ) ) [ 3 ] ) ;
5) 声明一个未命名的 显式对象参数
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 起)

函数声明中声明的参数名称通常仅用于自文档化目的。它们在函数定义中会被使用(但仍然是可选的)。

当类型名称嵌套在圆括号中时,参数列表会出现歧义 (包括 lambda表达式 (自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. 每个参数的类型 (包括函数 parameter packs (since C++11) 由其自身的 parameter declaration 确定。
  2. 确定每个参数的类型后,任何“ T 的数组”类型或函数类型 T 的参数会被调整为“指向 T 的指针”。
  3. 生成参数类型列表后,在构成函数类型时会删除修饰参数类型的任何顶层 cv-qualifiers
  4. 最终转换后的参数类型列表以及是否存在 ellipsis 或函数 parameter pack (since C++11) 即构成函数的parameter-type-list。
void f(char*);         // #1
void f(char[]) {}      // 定义 #1
void f(const char*) {} // 正确:另一个重载
void f(char* const) {} // 错误:重定义 #1
void g(char(*)[2]);   // #2
void g(char[3][2]) {} // 定义 #2
void g(char[3][3]) {} // 正确:另一个重载
void h(int x(const int)); // #3
void h(int (*)(int)) {}   // 定义 #3

确定函数类型

在语法 (1) 中,假设 noptr-declarator 作为独立声明,给定 noptr-declarator qualified-id unqualified-id 的类型为“派生声明符类型列表 T ”:

  • 若异常规范为 非抛出式 ,则所声明函数的类型为
    “派生声明符类型列表 noexcept 函数,其参数为
    参数类型列表 cv  (可选) ref   (可选) 并返回 T ”。
(C++17 起)
  • (C++17 前) 否则,该 (C++17 起) 函数声明的类型为
    “派生声明符类型列表 函数,接受
    参数类型列表 cv  (可选) 引用限定符   (可选) (C++11 起) 并返回 T ”。

在语法 (2) 中,假设 noptr-declarator 作为独立声明,给定 noptr-declarator qualified-id unqualified-id 的类型为“derived-declarator-type-list T ”(此时 T 必须为 auto ):

(C++11 起)
  • 若异常规范为 非抛出 ,则声明的函数类型为
    “derived-declarator-type-list noexcept 函数,其参数类型列表为
    parameter-type-list cv  (可选) ref   (可选) ,返回 trailing ”。
(C++17 起)
  • (C++17 前) (C++17 起)否则, 声明的函数类型为
    “derived-declarator-type-list 函数,其参数类型列表为
    parameter-type-list cv  (可选) ref   (可选) ,返回 trailing ”。

attr (若存在)应用于函数类型。

(C++11 起)
// “f1”的类型是
// “带有[[noreturn]]属性的返回void的int参数函数”
void f1(int a) [[noreturn]];
// “f2”的类型是
// “返回int的constexpr noexcept函数,参数为int数组指针”
constexpr auto f2(int[] b) noexcept -> int;
struct X
{
    // “f3”的类型是
    // “返回const int的无参数const成员函数”
    const int f3() const;
};

后置限定符

带有 cv  ref   (C++11 起) 限定符的函数类型(包括由 typedef 命名的类型)仅能出现在以下情形:

typedef int FIC(int) const;
FIC f;     // 错误:未声明成员函数
struct S
{
    FIC f; // 正确
};
FIC S::*pm = &S::f; // 正确

函数签名

每个函数都有一个签名。

函数的签名由其名称和 parameter-type-list 组成。其签名还包含所在的 namespace ,但存在以下例外情况:

  • 若该函数为 成员函数 ,其签名包含该函数所属的类而非外围命名空间。若存在以下组件,其签名亦包含:
  • cv
  • ref
(自 C++11 起)
  • 尾随 requires 子句
  • 若函数为带有尾随 requires 子句的非模板 friend 函数,其签名包含外围类而非外围命名空间。该签名亦包含尾随 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 - (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 是以下之一:

构造函数初始化列表  (可选) 复合语句 (1)
函数try块 (2)
= default ; (3) (自 C++11 起)
= delete ; (4) (自 C++11 起)
= delete ( 字符串字面量 ); (5) (自 C++26 起)
1) 常规函数体。
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;
}
// 声明说明符序列是 "int"
// 声明符是 "max(int a, int b, int c)"
// 函数体是 { ... }

函数体是一个 复合语句 (由一对花括号包围的零个或多个语句序列),在函数调用时执行。此外, 构造函数 的函数体还包含以下内容:

若函数定义包含 virt-specs ,则必须定义 成员函数

(C++11 起)

若函数定义包含 requires-clause ,则必须定义 模板函数

(C++20 起)
void f() override {} // 错误:非成员函数
void g() requires (sizeof(int) == 4) {} // 错误:非模板函数

函数定义的参数类型以及返回类型不能是(可能带有 cv 限定符的) 不完整 类类型 除非该函数被定义为被删除的 (C++11 起) 。完整性检查仅在函数体内进行,这允许 成员函数 返回其定义的类(或其外围类),即使该类型在定义点是不完整的(在函数体内它是完整的)。

在函数定义的 declarator 中声明的参数,在其函数体内部是 in scope 的。如果参数在函数体中未被使用,则无需为其命名(使用抽象声明符即可):

void print(int a, int) // 第二个参数未被使用
{
    std::printf("a = %d\n", a);
}

尽管在函数声明中会丢弃参数的顶层 cv限定符 ,但它们会修改在函数体内可见的参数类型:

void f(const int n) // 声明类型为 void(int) 的函数
{
    // 但在函数体内,“n”的类型为 const int
}

默认函数

如果函数定义采用语法 ( 3 ) ,则该函数被定义为 显式默认 函数。

显式默认化的函数必须是 特殊成员函数 比较运算符函数 (自 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 在其首次声明时被显式默认化,则将其定义为删除状态。
  • 否则,程序非良构。

在首次声明时显式默认化的函数会隐式地成为 inline 函数,并且如果满足条件,会隐式成为 constexpr 函数

struct S
{
    S(int a = 0) = default;             // 错误:存在默认参数
    void operator=(const S&) = default; // 错误:返回类型不匹配
    ~S() noexcept(false) = default;     // 正确:异常规范不同
private:
    int i;
    S(S&);          // 正确:私有拷贝构造函数
};
S::S(S&) = default; // 正确:定义拷贝构造函数

显式默认函数和隐式声明函数统称为 默认函数 。它们的实际定义将由编译器隐式提供,详见各自对应的页面说明。

已删除函数

如果函数定义采用语法 ( 4 ) ( 5 ) (C++26 起) ,则该函数被定义为 显式删除 的。

任何对被删除函数的使用都是不合法的(程序将无法编译)。这包括显式调用(使用函数调用运算符)和隐式调用(调用被删除的重载运算符、特殊成员函数、分配函数等),构造指向被删除函数的指针或成员指针,甚至在不属于 潜在求值 的表达式中使用被删除函数。

非纯虚成员函数可以被定义为删除的,即使它被隐式地 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"); // 自 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; // 错误:必须在首次声明时删除

用户提供的函数

如果一个函数是用户声明的,且在其首次声明时未被显式默认化或删除,那么它就是 user-provided 。用户提供的显式默认化函数(即在首次声明后被显式默认化的函数)在其被显式默认化的位置定义;如果此类函数被隐式定义为删除,则程序非良构。在首次声明后将函数声明为默认化,可以在保持稳定二进制接口以适应代码库演进的同时,提供高效的执行和简洁的定义。

// “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;

歧义解析

当函数体与以 { 开始的 初始化器 之间存在歧义时 ,或与以 = (自 C++26 起) ,该歧义通过检查 noptr-declarator 声明符标识符 类型来解决:

  • 如果类型是函数类型,则将歧义标记序列视为函数体。
  • 否则,将歧义标记序列视为初始化器。
using T = void(); // 函数类型
using U = int;    // 非函数类型
T a{}; // 定义不执行任何操作的函数
U b{}; // 值初始化 int 对象
T c = delete("hello"); // 将函数定义为已删除
U d = delete("hello"); // 使用 delete 表达式的结果对 int 对象进行拷贝初始化
                       // (格式错误)

__func__

在函数体内,函数局部预定义变量 __func__ 的定义方式如同

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

该变量具有块作用域和静态存储期:

struct S
{
    S(): s(__func__) {} // 正确:初始化列表属于函数体的一部分
    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 起)

函数契约说明符

函数声明和 lambda表达式 可以包含一系列 函数契约说明符  ,每个说明符具有以下语法:

pre 属性  (可选) ( 谓词 ) (1)
post 属性  (可选) ( 谓词 ) (2)
post 属性  (可选) ( 标识符 结果属性  (可选) : 谓词 ) (3)
1) 引入 前置条件断言 
2,3) 引入一个 后置条件断言 
2) 断言不绑定到结果。
3) 该断言绑定到结果。
attr - 应用于所引入合约断言的属性列表
predicate - 任意表达式(除未加括号的 逗号表达式 外)
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 作为关联函数的 结果绑定 名称。结果绑定表示该函数调用返回的对象或引用。结果绑定的类型是其关联函数的返回类型。

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”绑定任何值

当非模板函数的声明返回类型包含 占位符类型 时,带有 identifier  的后置条件断言只能出现在函数定义中:

auto g(auto&) post(r : r >= 0); // 正确:“g”是一个模板
auto h() post(r : r >= 0);      // 错误:无法命名返回值
auto k() post(r : r >= 0)       // 正确:“k”是一个定义
{
    return 0;
}

契约一致性

函数或函数模板 func 重声明 D 必须不包含 contract-specs ,或者与从 D 可达的任何首次声明 F 具有相同的 contract-specs 。如果 D F 位于不同翻译单元中,则仅当 D 附加到命名模块时才需要诊断。

如果声明 F1 是某个翻译单元中 func 的首个声明,而声明 F2 是另一个翻译单元中 func 的首个声明, F1 F2 必须指定相同的 contract-specs ,不要求诊断。

两个 contract-specs 如果包含相同顺序的相同函数契约说明符,则它们是相同的。

函数声明 D1 上的函数契约说明符 C1 与函数声明 D2 上的函数契约说明符 C2 相同,当且仅当满足以下所有条件:

  • C1 C2 predicate 若置于声明 D1 D2 的函数定义中(若 D1 D2 处于不同翻译单元,则各 predicate 内定义的对应实体行为如同存在具有单一定义的单一实体),应满足 one-definition rule ,但以下重命名情况除外:
    • 所声明函数的形参重命名。
    • 包围所声明函数的模板的模板参数重命名。
    • 结果绑定(若存在)的重命名。
  • C1 C2 要么均具有 identifier ,要么均不具有。

如果此条件未满足仅因两个lambda表达式的比较所致,且这些表达式包含在 predicate 中,则不需要诊断。

bool b1, b2;
void f() pre (b1) pre([]{ return b2; }());
void f(); // 正确:函数契约说明符被省略
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++ 标准。

问题报告 适用标准 已发布行为 正确行为
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-use会导致程序非法 此类odr-use豁免
于使用禁令
CWG 2044 C++14 返回 void 的函数进行返回类型推导时
若声明返回类型为 decltype ( auto ) 会失败
更新推导规则
以处理此情况
CWG 2081 C++14 函数重声明可以使用返回类型推导
即使初始声明未使用
不允许
CWG 2144 C++11 { } 在同一位置既可能是函数体也可能是初始化器 通过声明符标识符的
类型进行区分
CWG 2145 C++98 函数定义中的 声明符 不能带括号 允许
CWG 2259 C++11 关于带括号类型名的歧义解析规则未涵盖lambda表达式 已涵盖
CWG 2430 C++98 在类定义内的成员函数定义中,
由于 CWG 1824 的解决方案,
该类类型不能作为返回类型或参数类型
仅在函数体内
进行检查
CWG 2760 C++98 构造函数的函数体不包含未在常规函数体中指定的初始化 同时包含这些
初始化
CWG 2831 C++20 requires子句 的函数定义
可能定义非模板函数
禁止
CWG 2846 C++23 显式对象成员函数不能有类外定义 允许
CWG 2915 C++23 未命名的显式对象参数可以具有 void 类型 禁止

另请参阅

C 文档 关于 函数声明