命名空间
变体
操作

函数声明

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围 for (C++11 起)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (C++17 前已弃用*)
noexcept 说明符 (C++11 起)
异常
命名空间
类型
基本类型
枚举类型
函数类型
说明符
const/volatile
decltype (C++11 起)
auto (C++11 起)
constexpr (C++11 起)
consteval (C++20 起)
constinit (C++20 起)
存储期说明符
初始化
表达式
备选表示
字面量
布尔 - 整数 - 浮点
字符 - 字符串 - nullptr (C++11 起)
用户定义 (C++11 起)
工具
属性 (C++11 起)
类型
typedef 声明
类型别名声明 (C++11 起)
转型
内存分配
类特有的函数属性
虚函数
override 说明符 (C++11 起)  
final 说明符 (C++11 起)
explicit (C++11 起)
static

特殊成员函数
模板
模板特化
形参包 (C++11 起)
杂项
 
 

函数声明引入函数名及其类型。函数定义将函数名/类型与函数体关联起来。

内容

[编辑] 函数声明

函数声明可以出现在任何作用域中。类作用域中的函数声明引入一个类成员函数(除非使用了 friend 说明符),详情参见 成员函数友元函数

noptr-声明符 ( 形参列表 ) cv (可选) ref  (可选) except (可选) attr (可选) (1)
noptr-声明符 ( 形参列表 ) cv (可选) ref  (可选) except (可选) attr (可选)
-> 尾随
(2) (C++11 起)

(参见 声明声明符 语法的其他形式)

1) 常规函数声明符语法。
2) 尾随返回类型声明。在这种情况下,decl-说明符序列 必须包含关键字 auto
noptr-声明符 - 任何有效的 声明符,但如果它以 *&&& 开头,则必须用括号括起来。
形参列表 - 可能为空,函数形参的逗号分隔列表(详细信息见下文)
attr - (C++11 起) 属性 的列表。这些属性应用于函数的类型,而不是函数本身。函数的属性出现在声明符内的标识符之后,并与声明开头出现的任何属性组合。
cv - const/volatile 限定,仅允许在非静态成员函数声明中使用
ref - (C++11 起) ref-限定,仅允许在非静态成员函数声明中使用
except -

动态异常规范

(C++11 前)

要么是 动态异常规范
要么是 noexcept 规范

(C++11 起)
(C++17 前)

noexcept 规范

(C++17 起)
尾随 - 尾随返回类型,如果返回类型取决于实参名称(例如 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-说明符序列 包含关键字 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))

(注意:“const decltype(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 语句,或者 return 语句的实参是 void 表达式,则声明的返回类型必须是 decltype(auto)(在这种情况下,推导出的返回类型是 void)或(可能带有 cv 限定的)auto(在这种情况下,推导出的返回类型然后是(完全相同的 cv 限定的)void

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-说明符序列 声明符 (1)

attr (可选) this decl-说明符序列 声明符

(2) (C++23 起)
attr (可选) decl-说明符序列 声明符 = 初始化器 (3)
attr (可选) decl-说明符序列 抽象声明符 (可选) (4)

attr (可选) this decl-说明符序列 抽象声明符 (可选)

(5) (C++23 起)
attr (可选) decl-说明符序列 抽象声明符 (可选) = 初始化器 (6)
void (7)
1) 声明具名(形式)形参。有关 decl-说明符序列声明符 的含义,请参见 声明
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); (其中 Tvoid
void 形参是 显式对象形参 (C++23 起) int f5(this void);

尽管 decl-说明符序列 暗示可能存在类型说明符以外的说明符,但唯一允许的其他说明符是 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))声明显式对象形参

显式对象形参不能是 函数形参包,并且只能作为形参列表中第一个形参出现在以下声明中

带有显式对象形参的成员函数具有以下限制

  • 该函数不是 静态 的。
  • 该函数不是 虚函数
  • 函数的声明符不包含 cvref
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)) {}
                  // NOT void f(int C) {}
 
void g(int *(C[10])); // void g(int *(*fp)(C param[10]));
                      // NOT void g(int *C[10]);

形参类型不能是包含引用或指向未知边界数组的指针的类型,包括此类类型的多级指针/数组,或者是指向其形参是此类类型的函数的指针。

[编辑] 使用省略号

形参列表中的最后一个形参可以是省略号 (...);这声明了一个可变参数函数。可以省略省略号前的逗号(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

[编辑] 函数类型

[编辑] 形参类型列表

函数的形参类型列表按如下方式确定

  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

[编辑] 确定函数类型

在语法 (1) 中,假设 noptr-声明符 作为独立声明,给定 noptr-声明符限定标识非限定标识 的类型为 “derived-declarator-type-list T

  • 如果异常规范是 非抛出 的,则声明的函数类型是
    “derived-declarator-type-list noexcept 函数,它接受
    形参类型列表 cv (可选) ref  (可选) 并返回 T”。
(C++17 起)
  • (C++17 前)否则,(C++17 起) 声明的函数类型是
    “derived-declarator-type-list 函数,它接受
    形参类型列表 cv (可选) ref  (可选)(C++11 起) 并返回 T”。

在语法 (2) 中,假设 noptr-声明符 作为独立声明,给定 noptr-声明符限定标识非限定标识 的类型为 “derived-declarator-type-list T”(在这种情况下,T 必须是 auto

(C++11 起)
  • 如果异常规范是 非抛出 的,则声明的函数类型是
    “derived-declarator-type-list noexcept 函数,它接受
    形参类型列表 cv (可选) ref  (可选) 并返回 尾随 ”。
(C++17 起)
  • (C++17 前)否则,(C++17 起) 声明的函数类型是
    “derived-declarator-type-list 函数,它接受
    形参类型列表 cv (可选) ref  (可选) 并返回 尾随 ”。

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

(C++11 起)
// the type of “f1” is
// “function of int returning void, with attribute noreturn”
void f1(int a) [[noreturn]];
 
// the type of “f2” is
// “constexpr noexcept function of pointer to int returning int”
constexpr auto f2(int[] b) noexcept -> int;
 
struct X
{
    // the type of “f3” is
    // “function of no parameter const returning const int”
    const int f3() const;
};

[编辑] 尾随限定符

带有 cv ref  (C++11 起) 的函数类型(包括由 typedef 名称命名的类型)只能作为

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

[编辑] 函数签名

每个函数都有一个签名。

函数的签名由其名称和 形参类型列表 组成。其签名还包含外围 命名空间,但以下情况除外:

  • 如果函数是成员函数,则其签名包含函数所属的类,而不是外围命名空间。其签名还包含以下组件(如果存在):
  • cv
  • ref
(C++11 起)
  • 尾随 requires 子句
  • 如果函数是带有尾随 requires 子句的非模板 友元 函数,则其签名包含外围类,而不是外围命名空间。签名还包含尾随 requires 子句。
(C++20 起)

exceptattr(自 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 起) overridefinal 或它们的任意顺序组合
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) 常规函数体。
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 { ... }

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

如果函数定义包含 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 限定的)不完整 类类型,除非该函数被定义为 deleted(自 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 允许存在以下差异:

  • F1F2 可能具有不同的 ref 和/或 except
  • 如果 F2 具有 const C& 类型的非对象形参,则 F1 的相应非对象形参可能是 C& 类型。
  • 如果 F2 具有类型为 “C 的引用” 的隐式对象形参,则 F1 可能是显式对象成员函数,其 显式对象形参 的类型为(可能不同的)“C 的引用”,在这种情况下,F1 的类型将与 F2 的类型不同,因为 F1 的类型具有额外的形参。
(C++23 起)

如果 F1 的类型与 F2 的类型不同,但不是前面规则允许的方式,则:

  • 如果 F1 是赋值运算符,并且 F1 的返回类型与 F2 的返回类型不同,或者 F1 的非对象形参类型不是引用,则程序是非良构的。
  • 否则,如果 F1 在其首次声明中被显式预置,则它被定义为 deleted。
  • 否则,程序是非良构的。

在其首次声明中显式预置的函数是隐式 inline 的,并且如果它可以是 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 起),则该函数被定义为显式删除

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

非纯虚成员函数可以定义为 deleted,即使它是隐式 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”

函数的已删除定义必须是翻译单元中的第一个声明:先前声明的函数不能被重新声明为 deleted。

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

用户提供的函数

如果函数是用户声明的,并且在其首次声明中未显式预置或删除,则该函数是用户提供的。用户提供的显式预置函数(即,在其首次声明之后显式预置的函数)在其显式预置的点定义;如果此类函数被隐式定义为 deleted,则程序是非良构的。在函数的首次声明之后将其声明为预置可以提供高效的执行和简洁的定义,同时为不断发展的代码库启用稳定的二进制接口。

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

函数契约说明符

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

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;
}

契约一致性

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

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

两个 contract-specs s 是相同的,如果它们由相同顺序的相同函数契约说明符组成。

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

  • 如果将 C1C2predicate s 放置在声明 D1D2 上的函数定义中,则它们将满足 单一定义规则(如果 D1D2 在不同的翻译单元中,则每个 predicate 中定义的相应实体表现得好像存在具有单个定义的单个实体),除了以下重命名:
    • 声明函数的形参的重命名。
    • 包围声明函数的模板的模板形参的重命名。
    • 结果绑定(如果有)的重命名。
  • C1C2 都具有 identifier 或都不具有。

如果仅由于比较两个 lambda 表达式(它们包含在 predicate s 中)而未满足此条件,则不需要诊断。

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 预置或删除的函数不能用 overridefinal 指定
允许
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 如果声明的返回类型为 decltype(auto),则返回 void 的函数的返回类型推导将失败
更新了推导规则以处理这种情况
CWG 2081 C++14 函数重新声明可以使用返回类型推导,即使初始声明不使用
不允许
CWG 2144 C++11 {} 在同一位置可能是函数体或初始化器
通过声明符标识符的类型来区分
CWG 2145 C++98 函数定义中的 declarator 不能带括号 允许
CWG 2259 C++11 关于带括号的类型名称的歧义消除规则不涵盖 lambda 表达式
已涵盖
CWG 2430 C++98 在类定义中的成员函数定义中,由于 CWG issue 1824 的解决,该类的类型不能是返回类型或形参类型

仅在函数定义的上下文中检查
函数体
CWG 2760 C++98 构造函数的函数体不包括构造函数的常规函数体中未指定的初始化
也包括这些初始化
CWG 2831 C++20 带有 requires-clause 的函数定义可以定义非模板函数
禁止
CWG 2846 C++23 显式对象成员函数不能具有类外定义 允许
CWG 2915 C++23 未命名的显式对象形参可以具有 void 类型 禁止

[编辑] 参见

C 文档 关于 声明函数