命名空间
变体
操作

函数声明

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

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

目录

[编辑] 函数声明

函数声明可以出现在任何作用域中。类作用域中的函数声明引入一个类成员函数(除非使用了 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 - 可能为空的,逗号分隔的函数参数列表(详情请参见下文)
属性 - (C++11 起) 一个属性列表。这些属性应用于函数类型,而非函数本身。函数的属性出现在声明符中标识符之后,并与声明开头出现的任何属性(如果有)相结合。
cv - const/volatile 限定符,仅允许在非静态成员函数声明中使用
ref - (C++11 起) ref 限定符,仅允许在非静态成员函数声明中使用
异常规范 -

动态异常规范

(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-qualified 对象类型作为参数类型或返回类型已被弃用。

(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),则返回类型与 decltype 封装 return 语句中使用的表达式后获得的类型相同

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-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-seqdeclarator 的含义请参见声明
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-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))声明一个*显式对象参数*。

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

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

  • 该函数不是静态的
  • 该函数不是虚的
  • 函数的声明符不包含 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 起)。在这种情况下,选择是在声明一个函数指针类型的参数和声明一个在 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]);

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

[编辑] 使用省略号

参数列表中的最后一个参数可以是省略号(...);这声明了一个变参函数。省略号前的逗号可以省略(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-declarator 是一个独立的声明,给定 noptr-declarator 中的 qualified-idunqualified-id 的类型为“derived-declarator-type-list T

  • 如果异常规范是不抛出的,则声明的函数类型为
    “derived-declarator-type-list noexcept function of
    parameter-type-list cv (可选) ref  (可选) returning T”。
(C++17 起)
  • (在 C++17 之前)(C++17 前)否则,函数的(C++17 起)类型为
    “derived-declarator-type-list function of
    parameter-type-list cv (可选) ref  (可选)(C++11 起) returning T”。

在语法 (2) 中,假设 noptr-declarator 是一个独立的声明,给定 noptr-declarator 中的 qualified-idunqualified-id 的类型为“derived-declarator-type-list T”(在此情况下 T 必须是 auto

(C++11 起)
  • 如果异常规范是不抛出的,则声明的函数类型为
    “derived-declarator-type-list noexcept function of
    parameter-type-list cv (可选) ref  (可选) returning trailing ”。
(C++17 起)
  • (在 C++17 之前)(C++17 前)否则,函数的(C++17 起)类型为
    “derived-declarator-type-list function of
    parameter-type-list cv (可选) ref  (可选) returning trailing ”。

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) 有约束的函数定义。
属性 - (C++11 起) 属性列表。这些属性与 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 - 成员初始化列表,仅允许在构造函数中使用
复合语句 - 由一对花括号包围的语句序列,构成函数的主体
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 限定符的)不完整类类型,除非该函数定义为已删除(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 在其首次声明时被显式默认,它被定义为已删除。
  • 否则,程序格式错误。

一个在其首次声明时被显式默认的函数是隐式 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 起),则该函数被定义为显式删除

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

非纯虚成员函数可以定义为已删除,即使它被隐式 odr-used。已删除函数只能被已删除函数覆盖,非已删除函数只能被非已删除函数覆盖。

如果存在 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 起)

函数契约说明符

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

pre attr (可选) ( predicate ) (1)
post attr (可选) ( predicate ) (2)
post attr (可选) ( identifier result-attr (可选) : predicate ) (3)
1) 引入前置条件断言
2,3) 引入后置条件断言
2) 断言不绑定到结果。
3) 断言绑定到结果。
属性 - 适用于引入的契约断言的属性列表。
谓词 - 任何表达式(除了未加括号的逗号表达式
标识符 - 指代结果的标识符
结果属性 - 适用于结果绑定的属性列表


前置条件断言和后置条件断言统称为函数契约断言

函数契约断言是与函数相关联的契约断言。函数契约断言的谓词是其 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 由相同顺序的相同函数契约说明符组成,则它们是相同的。

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

  • 如果将 C1C2predicate 分别放在声明 D1D2 的函数定义中,它们将满足一次定义规则(如果 D1D2 在不同的翻译单元中,则每个 predicate 内定义的相应实体表现为只有一个具有单个定义的实体),除了以下重命名:
    • 声明函数的参数的重命名。
    • 包含声明函数的模板的模板参数的重命名。
    • 结果绑定(如果有)的重命名。
  • C1C2 都具有 identifier,或者两者都没有。

如果此条件仅由于比较 predicate 中包含的两个 lambda 表达式而未满足,则无需诊断。

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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
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-use 是非良构的
已删除虚函数的隐式odr-use是格式错误的
此类 odr-use 免于使用禁止
此类odr-use不受禁止
CWG 2044 C++14 返回 void 的函数的返回类型推导
如果声明的返回类型是 decltype(auto),则会失败
更新推导
规则以处理这种情况
CWG 2081 C++14 函数重声明可以使用返回类型
推导,即使初始声明没有
不允许
CWG 2144 C++11 {} 可以在同一位置是函数体或初始化器 通过声明符标识符的类型来区分
通过声明符标识符的类型区分
CWG 2145 C++98 函数定义中的 declarator 不能加括号 允许
CWG 2259 C++11 关于带括号的类型名称的歧义解决规则
不包括 lambda 表达式
已涵盖
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 文档 关于 声明函数