命名空間
變體
動作

函式宣告

出自 cppreference.com
< 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)
儲存期指定符
初始化
 
 

函式宣告引入函式名稱及其型別。函式定義將函式名稱/型別與函式主體關聯起來。

目錄

[編輯] 函式宣告

函式宣告可以出現在任何作用域中。類別作用域中的函式宣告引入一個類別成員函式 (除非使用 friend 指定符),詳情請參閱成員函式友元函式

noptr-declarator ( parameter-list ) cv (可選) ref  (可選) except (可選) attr (可選) (1)
noptr-declarator ( parameter-list ) cv (可選) ref  (可選) except (可選) attr (可選)
-> trailing
(2) (C++11 起)

(請參閱宣告以了解宣告子語法的其他形式)

1) 常規函式宣告子語法。
2) 尾部回傳型別宣告。在此情況下,decl-specifier-seq 必須包含關鍵字 auto
noptr-declarator - 任何有效的宣告子,但如果它以 *&&& 開頭,則必須用括號括起來。
參數列表 - 函式參數的列表,可能為空,以逗號分隔 (詳情請參閱下文)
屬性 - (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-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 參數的其他用法都是格式不正確的 (ill-formed)
錯誤用法 範例
存在多個參數 int f1(void, int);
命名了 void 參數 int 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))宣告一個顯式物件參數

顯式物件參數不能是函式參數包,且它只能作為參數列表的第一個參數出現在以下宣告中

帶有顯式物件參數的成員函式有以下限制

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-declarator 作為一個獨立的宣告,給定 noptr-declaratorqualified-idunqualified-id 的型別為「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-declarator 作為一個獨立的宣告,給定 noptr-declaratorqualified-idunqualified-id 的型別為「derived-declarator-type-list T」(在此情況下,T 必須是 auto

(C++11 起)
  • 如果異常規格是不拋出異常的,則宣告的函式型別為
    「derived-declarator-type-list noexcept 函式,其
    參數型別列表 cv (可選) ref  (可選) 回傳 trailing」。
(自 C++17 起)
  • 函式的(C++17 前)否則,函式的(C++17 起)宣告型別為
    「derived-declarator-type-list 函式,其
    參數型別列表 cv (可選) ref  (可選) 回傳 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 起) 一個屬性列表。這些屬性與宣告子中識別符之後的屬性(如果有的話,請參閱本頁頂部)合併。
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 區塊
字串字面值 - 一個未求值字串字面值,可用於解釋函式被刪除的原因
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 不同,如下所示

  • F1F2 可能有不同的 ref 和/或 except
  • 如果 F2 有一個型別為 const C& 的非物件參數,則 F1 的相應非物件參數可能是型別 C&
  • 如果 F2 有一個型別為「參考到 C」的隱式物件參數,則 F1 可能是一個顯式物件成員函式,其顯式物件參數是(可能不同)型別「參考到 C」,在這種情況下,F1 的型別會與 F2 的型別不同,因為 F1 的型別會多一個參數。
(自 C++23 起)

如果 F1 的型別以非上述規則允許的方式與 F2 的型別不同,則

  • 如果 F1 是一個賦值運算符,且 F1 的回傳型別與 F2 的回傳型別不同,或者 F1 的非物件參數型別不是參考,則程式格式不正確 (ill-formed)。
  • 否則,如果 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 起),則該函式被定義為顯式刪除

任何使用刪除函式的行為都是格式不正確的 (ill-formed)(程式將無法編譯)。這包括顯式呼叫(使用函式呼叫運算符)和隱式呼叫(呼叫已刪除的重載運算符、特殊成員函式、記憶體分配函式等),構造指向已刪除函式的指標或成員指標,甚至在不是潛在求值的表達式中使用已刪除函式。

非純虛擬成員函式可以被定義為刪除,即使它會被隱式地odr-使用。已刪除的函式只能被已刪除的函式覆蓋,而非刪除的函式只能被非刪除的函式覆蓋。

如果存在string-literal,則鼓勵實作將其文本作為結果診斷訊息的一部分,以顯示刪除的理由或建議替代方案。

(C++26 起)

如果函式被重載,則首先進行重載解析,並且只有在選中已刪除函式時,程式才是格式不正確的 (ill-formed)。

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

使用者提供函式

如果函式是使用者宣告的,且在其首次宣告時沒有顯式預設或刪除,則它被視為使用者提供的。使用者提供且顯式預設的函式(即在首次宣告之後顯式預設的函式)在其顯式預設點被定義;如果此類函式被隱式定義為刪除,則程式格式不正確 (ill-formed)。在首次宣告之後將函式宣告為預設,可以提供高效執行和簡潔定義,同時為不斷演進的程式碼庫啟用穩定的二進位介面。

// 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 - 任何表達式(除了未加括號的逗號表達式
識別字 - 指代結果的識別符
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,則程式格式不正確 (ill-formed)

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++ 標準。

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 回傳 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 語言文件中關於宣告函式
English Deutsch 日本語 中文(简体) 中文(繁體)