名稱空間
變體
操作

函式宣告

來自 cppreference.com
< 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 文件 關於 宣告函式