名稱空間
變體
操作

過載決議

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

為了編譯函式呼叫,編譯器必須首先執行名字查詢,對於函式,可能涉及依賴於引數的查詢,對於函式模板,可能接著進行模板引數推導

如果名字指代多個實體,則稱其為*過載*,編譯器必須確定呼叫哪個過載。簡單來說,呼叫引數與實參最匹配的過載。

具體來說,過載決議按以下步驟進行:

  1. 構建候選函式集合。
  2. 將集合修剪為僅包含可行函式
  3. 分析集合以確定唯一的最佳可行函式(這可能涉及隱式轉換序列的排序)。
void f(long);
void f(float);
 
f(0L); // calls f(long)
f(0);  // error: ambiguous overload

除了函式呼叫,過載函式名還可能出現在其他幾個上下文中,這些上下文適用不同的規則:參見過載函式的地址

如果函式不能透過過載決議被選中,則它不能被使用(例如,它是一個模板實體,其約束失敗)。

目錄

[編輯] 候選函式

在過載決議開始之前,透過名字查詢和模板引數推導選擇的函式被組合以形成*候選函式*集合。具體細節取決於過載決議發生的上下文。

[編輯] 對具名函式的呼叫

如果在函式呼叫表示式 E(args) 中,E 命名了一組過載函式和/或函式模板(但不是可呼叫物件),則遵循以下規則:

  • 如果表示式 E 的形式為 PA->BA.B (其中 A 具有類型別 cv T),則 B查詢T 的成員函式。透過該查詢找到的函式宣告是候選函式。用於過載決議的引數列表具有型別為 cv T 的隱含物件引數。
  • 如果表示式 E 是一個主要表示式,則名字根據函式呼叫的正常規則(可能涉及ADL)進行查詢。透過此查詢找到的函式宣告(由於查詢的工作方式)要麼是:
  • 所有非成員函式(在這種情況下,用於過載決議的引數列表與函式呼叫表示式中使用的引數列表完全相同)
  • 某個類 T 的所有成員函式,在這種情況下,如果 this 在作用域內並且是指向 TT 的派生類的指標,則 *this 用作隱含物件引數。否則(如果 this 不在作用域內或不指向 T),則使用型別為 T 的虛構物件作為隱含物件引數,如果過載決議隨後選擇非靜態成員函式,則程式格式錯誤。

[編輯] 對類物件的呼叫

如果在函式呼叫表示式 E(args) 中,E 具有類型別 cv T,則:

  • T 的函式呼叫運算子是透過在表示式 (E).operator() 的上下文中普通查詢名稱 operator() 獲得的,並且找到的每個宣告都新增到候選函式集合中。
  • 對於 TT 的基類中每個非explicit使用者定義轉換函式(除非被隱藏),其 cv-限定符與 T 的 cv-限定符相同或更高,並且該轉換函式轉換為:
  • 函式指標
  • 指向函式的指標的引用
  • 函式引用
然後,一個具有唯一名稱的*代理呼叫函式*被新增到候選函式集合中,其第一個引數是轉換結果,其餘引數是轉換結果接受的引數列表,返回型別是轉換結果的返回型別。如果此代理函式被後續的過載決議選中,則將呼叫使用者定義轉換函式,然後呼叫轉換結果。

在任何情況下,用於過載決議的引數列表是函式呼叫表示式的引數列表,前面是隱含物件引數 E (當與代理函式匹配時,使用者定義轉換將自動將隱含物件引數轉換為代理函式的第一個引數)。

int f1(int);
int f2(float);
 
struct A
{
    using fp1 = int(*)(int);
    operator fp1() { return f1; } // conversion function to pointer to function
    using fp2 = int(*)(float);
    operator fp2() { return f2; } // conversion function to pointer to function
} a;
 
int i = a(1); // calls f1 via pointer returned from conversion function

[編輯] 對過載運算子的呼叫

如果表示式中運算子的至少一個引數具有類型別或列舉型別,則內建運算子使用者定義的運算子過載都參與過載決議,候選函式集選擇如下:

對於引數型別為 T1 的一元運算子 @ (移除 cv-限定符後),或左運算元型別為 T1 且右運算元型別為 T2 的二元運算子 @ (移除 cv-限定符後),準備以下幾組候選函式:

1) *成員候選*:如果 T1 是完整類或當前正在定義的類,則成員候選集是 T1::operator@限定名查詢結果。在所有其他情況下,成員候選集為空。
2) *非成員候選*:對於允許非成員形式的運算子過載,透過在表示式上下文中非限定名查詢 operator@ 找到的所有宣告(可能涉及ADL),但成員函式宣告被忽略,並且不會阻止查詢繼續到下一個封閉作用域。如果二元運算子的兩個運算元或一元運算子的唯一運算元具有列舉型別,則查詢集中只有引數具有該列舉型別(或對該列舉型別的引用)的函式才能成為非成員候選。
3) *內建候選*:對於 operator,、一元 operator&operator->,內建候選集為空。對於其他運算子,只要所有運算元都可以隱式轉換為其引數,內建候選就是內建運算子頁面中列出的那些。如果任何內建候選與非成員候選或重寫非成員候選(C++20 起)(非函式模板特化)具有相同的引數列表,則它不會新增到內建候選列表中。當考慮內建賦值運算子時,對其第一個引數的轉換受到限制:只考慮標準轉換序列
4) *重寫候選*
  • 對於四個關係運算符表示式 x < yx <= yx > yx >= y,找到的所有成員、非成員和內建 operator<=> 都新增到集合中。
  • 對於四個關係運算符表示式 x < yx <= yx > yx >= y,以及三路比較表示式 x <=> y,為找到的每個成員、非成員和內建 operator<=> 新增一個合成的候選,其兩個引數的順序相反。
  • 對於 x != y,找到的所有成員、非成員和內建 operator== 都新增到集合中,除非存在匹配的 operator!=
  • 對於相等運算子表示式 x == yx != y,為找到的每個成員、非成員和內建 operator== 新增一個合成的候選,其兩個引數的順序相反,除非存在匹配的 operator!=
在所有情況下,重寫候選在重寫表示式的上下文中不被考慮。對於所有其他運算子,重寫候選集為空。
(C++20 起)

提交過載決議的候選函式集是上述集合的並集。用於過載決議的引數列表由運算子的運算元組成,但 operator-> 除外,其中第二個運算元不是函式呼叫的引數(參見成員訪問運算子)。

struct A
{
    operator int();              // user-defined conversion
};
A operator+(const A&, const A&); // non-member user-defined operator
 
void m()
{
    A a, b;
    a + b; // member-candidates: none
           // non-member candidates: operator+(a, b)
           // built-in candidates: int(a) + int(b)
           // overload resolution chooses operator+(a, b)
}

如果過載決議選擇了一個內建候選,則不允許來自類型別運算元的使用者定義轉換序列具有第二個標準轉換序列:使用者定義轉換函式必須直接給出預期的運算元型別。

struct Y { operator int*(); }; // Y is convertible to int*
int *a = Y() + 100.0;          // error: no operator+ between pointer and double

對於 operator,、一元 operator&operator->,如果候選函式集中沒有可行函式(見下文),則運算子被重新解釋為內建運算子。

如果過載決議為運算子 @ 選擇了重寫的 operator<=> 候選,則 x @ y 被解釋為重寫表示式:如果選定的候選是引數順序顛倒的合成候選,則為 0 @ (y <=> x),否則為 (x <=> y) @ 0,使用選定的重寫 operator<=> 候選。

如果過載決議為運算子 @(即 ==!=)選擇了重寫的 operator== 候選,則其返回型別必須是(可能 cv-限定的)bool,並且 x @ y 被解釋為重寫表示式:如果選定的候選是引數順序顛倒的合成候選,則為 y == x!(y == x),否則為 !(x == y),使用選定的重寫 operator== 候選。

在這種情況下,過載決議有一個最終的決勝規則:非重寫候選優先於重寫候選,非合成重寫候選優先於合成重寫候選。

這種反轉引數順序的查詢使得只需編寫 operator<=>(std::string, const char*)operator==(std::string, const char*) 即可生成 std::stringconst char* 之間的所有比較(雙向)。更多詳情請參見預設比較

(C++20 起)

[編輯] 透過建構函式初始化

當類型別物件被直接初始化預設初始化(包括在複製列表初始化上下文中進行預設初始化)(C++11 起)時,候選函式是正在初始化的類的所有建構函式。引數列表是初始化器的表示式列表。

否則,候選函式是正在初始化的類的所有轉換建構函式。引數列表是初始化器的表示式。

對於複製列表初始化上下文中的預設初始化,如果選擇了explicit建構函式,則初始化格式錯誤。

(C++11 起)

[編輯] 透過轉換進行復制初始化

如果類型別物件的複製初始化需要呼叫使用者定義轉換將型別為 cv S 的初始化器表示式轉換為正在初始化的物件的型別 cv T,則以下函式是候選函式:

  • T 的所有轉換建構函式
  • S 及其基類(除非被隱藏)到 TT 的派生類或對它們的引用的非explicit轉換函式。如果此複製初始化是 cv T 的直接初始化序列的一部分(初始化要繫結到接受 cv T 引用的建構函式的第一個引數的引用),則也會考慮顯式轉換函式。

無論是哪種情況,用於過載決議的引數列表都包含一個單獨的引數,即初始化器表示式,它將與建構函式的第一個引數或轉換函式的隱式物件引數進行比較。

[編輯] 透過轉換進行非類初始化

當非類型別 cv1 T 的物件初始化需要使用者定義轉換函式將類型別 cv S 的初始化器表示式轉換為 cv1 T 時,以下函式是候選函式:

  • S 及其基類(除非被隱藏)的非顯式使用者定義轉換函式,它們產生型別 T 或可透過標準轉換序列轉換為 T 的型別,或對此類型別的引用。返回型別上的 cv-限定符在選擇候選函式時被忽略。
  • 如果這是直接初始化,則 S 及其基類(除非被隱藏)的顯式使用者定義轉換函式,它們產生型別 T 或可透過資格轉換轉換為 T 的型別,或對此類型別的引用,也考慮在內。

無論是哪種情況,用於過載決議的引數列表都包含一個單獨的引數,即初始化器表示式,它將與轉換函式的隱式物件引數進行比較。

[編輯] 透過轉換進行引用初始化

引用初始化期間,當對 cv1 T 的引用繫結到從類型別 cv2 S 的初始化器表示式轉換的左值或右值結果時,選擇以下函式作為候選集:

  • S 及其基類(除非被隱藏)的非顯式使用者定義轉換函式,轉換為以下型別:
  • (當轉換為左值時)左值引用到 cv2 T2
  • (當轉換為右值或函式型別的左值時)cv2 T2 或右值引用到 cv2 T2
其中 cv2 T2cv1 T 引用相容
  • 對於直接初始化,如果 T2T 是相同的型別,或者可以透過資格轉換轉換為型別 T,則也考慮顯式使用者定義轉換函式。

無論是哪種情況,用於過載決議的引數列表都包含一個單獨的引數,即初始化器表示式,它將與轉換函式的隱式物件引數進行比較。

[編輯] 列表初始化

當非聚合類型別 T 的物件被列表初始化時,會發生兩階段過載決議。

  • 在階段 1,候選函式是 T 的所有初始化列表建構函式,用於過載決議的引數列表包含一個單獨的初始化列表引數。
  • 如果在階段 1 過載決議失敗,則進入階段 2,其中候選函式是 T 的所有建構函式,用於過載決議的引數列表包含初始化列表的各個元素。

如果初始化列表為空且 T 具有預設建構函式,則跳過階段 1。

在複製列表初始化中,如果階段 2 選擇了顯式建構函式,則初始化格式錯誤(與所有其他複製初始化不同,在其他情況下甚至不考慮顯式建構函式)。

[編輯] 函式模板候選的附加規則

如果名字查詢找到了函式模板,則執行模板引數推導並檢查任何顯式模板引數,以找到在這種情況下可以使用的模板引數值(如果有的話)。

  • 如果兩者都成功,則使用模板引數合成相應函式模板特化的宣告,並將其新增到候選集合中,並且此類特化被視為非模板函式,除非在下面的決勝規則中另有規定。
  • 如果引數推導失敗或合成的函式模板特化格式錯誤,則不會將此類函式新增到候選集合中(參見SFINAE)。

如果一個名字指代一個或多個函式模板以及一組過載的非模板函式,則這些函式和從模板生成的特化都是候選函式。

有關更多詳細資訊,請參見函式模板過載

如果建構函式模板或轉換函式模板具有條件顯式說明符,並且該說明符是值依賴的,則在推導之後,如果上下文需要一個非顯式候選,並且生成的特化是顯式的,則將其從候選集合中刪除。

(C++20 起)

[編輯] 建構函式候選的附加規則

預設的移動建構函式移動賦值運算子被定義為已刪除的,從候選函式集合中排除。

從類型別 C 繼承的建構函式,如果其第一個引數的型別是“對 P 的引用”(包括從模板例項化的此類建構函式),在構造型別為 D 的物件時,如果滿足以下所有條件,則將其從候選函式集合中排除:

  • 引數列表恰好有一個引數。
  • CP 引用相關
  • PD 引用相關。
(C++11 起)

[編輯] 成員函式候選的附加規則

如果任何候選函式是成員函式(靜態或非靜態)且沒有顯式物件引數(C++23 起),但不是建構函式,則將其視為具有一個額外的引數(*隱式物件引數*),該引數表示呼叫它們的物件,並出現在實際引數的第一個之前。

類似地,被呼叫成員函式的物件作為*隱含物件引數*被預先新增到引數列表中。

對於類 X 的成員函式,隱式物件引數的型別受成員函式的 cv-限定符和引用限定符的影響,如成員函式中所述。

使用者定義轉換函式被視為*隱含物件引數*的成員,以便確定*隱式物件引數*的型別。

透過 using 宣告引入到派生類中的成員函式被視為派生類的成員,以便定義*隱式物件引數*的型別。

對於靜態成員函式,*隱式物件引數*被認為與任何物件匹配:不檢查其型別,也不嘗試對其進行轉換序列。

(直至 C++23)

對於過載決議的其餘部分,*隱含物件引數*與其他引數無異,但以下特殊規則適用於*隱式物件引數*:

1) 使用者定義轉換不能應用於隱式物件引數。
2) 右值可以繫結到非 const 隱式物件引數(除非這是針對引用限定的成員函式)(C++11 起),並且不影響隱式轉換的排序。
struct B { void f(int); };
struct A { operator B&(); };
 
A a;
a.B::f(1); // Error: user-defined conversions cannot be applied
           // to the implicit object parameter
static_cast<B&>(a).f(1); // OK

[編輯] 可行函式

給定如上所述構建的候選函式集,過載決議的下一步是檢查實參和形參,以將集合縮減為*可行函式*集。

要包含在可行函式集中,候選函式必須滿足以下條件:

1) 如果有 M 個實參,則恰好具有 M 個形參的候選函式是可行的。
2) 如果候選函式少於 M 個形參,但具有省略號引數,則它是可行的。
3) 如果候選函式多於 M 個形參,並且第 M+1 個形參以及所有後續形參都具有預設實參,則它是可行的。對於過載決議的其餘部分,形參列表在 M 處截斷。
4) 如果函式具有相關約束,則必須滿足該約束。
(C++20 起)
5) 對於每個實參,必須至少有一個隱式轉換序列將其轉換為相應的形參。
6) 如果任何形參具有引用型別,則在此步驟中考慮引用繫結:如果右值實參對應於非 const 左值引用形參,或左值實參對應於右值引用形參,則函式不可行。

使用者定義轉換(包括轉換建構函式和使用者定義轉換函式)禁止參與隱式轉換序列,因為這會使得應用多個使用者定義轉換成為可能。具體來說,如果轉換的目標是建構函式的第一個引數或使用者定義轉換函式的隱式物件引數,並且該建構函式/使用者定義轉換是以下情況的候選,則不考慮它們:

  • 透過列表初始化進行初始化,其中初始化列表恰好有一個元素,該元素本身是一個初始化列表,並且目標是類 X 的建構函式的第一個引數,並且轉換是到 X 或對(可能 cv-限定的)X 的引用。
struct A { A(int); };
struct B { B(A); };
 
B b{{0}}; // list-initialization of B
 
// candidates: B(const B&), B(B&&), B(A)
// {0} -> B&& not viable: would have to call B(A)
// {0} -> const B&: not viable: would have to bind to rvalue, would have to call B(A)
// {0} -> A viable. Calls A(int): user-defined conversion to A is not banned
(C++11 起)

[編輯] 最佳可行函式

對於每對可行函式 F1F2,從第 i 個實參到第 i 個形參的隱式轉換序列被排序,以確定哪個更好(除了第一個實參,靜態成員函式的*隱式物件實參*對排序沒有影響)。

如果 F1 的所有實參的隱式轉換不比 F2 的所有實參的隱式轉換*差*,並且滿足以下任一條件,則 F1 被確定為比 F2 更好的函式:

1) F1 至少有一個實參的隱式轉換比 F2 該實參的相應隱式轉換*更好*,或者,如果不是這樣,
2) (僅在透過轉換進行非類初始化的情況下),從 F1 的結果到被初始化型別的標準轉換序列比從 F2 的結果的標準轉換序列*更好*,或者,如果不是這樣,
3) (僅在透過轉換函式進行函式型別引用的直接引用繫結的情況下),F1 的結果與正在初始化的引用是相同型別的引用(左值或右值),而 F2 的結果不是,或者,如果不是這樣,
(C++11 起)
4) F1 是非模板函式而 F2 是模板特化,或者,如果不是這樣,
5) F1F2 都是模板特化,並且根據模板特化的偏序規則F1 更特化,或者,如果不是這樣,
6) F1F2 都是非模板函式,並且 F1F2 受到更多的偏序約束
template<typename T = int>
struct S
{
    constexpr void f(); // #1
    constexpr void f(this S&) requires true; // #2
};
 
void test()
{
    S<> s;
    s.f(); // calls #2
}
或者,如果不是這樣,
(C++20 起)


7) F1 是類 D 的建構函式,F2D 的基類 B 的建構函式,並且對於所有引數,F1F2 的相應引數具有相同的型別。
struct A
{
    A(int = 0);
};
 
struct B: A
{
    using A::A;
 
    B();
};
 
B b; // OK, B::B()
或者,如果不是這樣,
(C++11 起)


8) F2 是重寫候選而 F1 不是,或者,如果不是這樣,
9) F1F2 都是重寫候選,並且 F2 是引數順序顛倒的合成重寫候選而 F1 不是,或者,如果不是這樣,
(C++20 起)


10) F1 是從使用者定義的推導指南生成的而 F2 不是,或者,如果不是這樣,
11) F1複製推導候選F2 不是,或者,如果不是這樣,
12) F1 是從非模板建構函式生成的而 F2 是從建構函式模板生成的。
template<class T>
struct A
{
    using value_type = T;
    A(value_type);  // #1
    A(const A&);    // #2
    A(T, T, int);   // #3
 
    template<class U>
    A(int, T, U);   // #4
};                  // #5 is A(A), the copy deduction candidate
 
A x(1, 2, 3); // uses #3, generated from a non-template constructor
 
template<class T>
A(T) -> A<T>;       // #6, less specialized than #5
 
A a (42); // uses #6 to deduce A<int> and #1 to initialize
A b = a;  // uses #5 to deduce A<int> and #2 to initialize
 
template<class T>
A(A<T>) -> A<A<T>>; // #7, as specialized as #5
A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize
(C++17 起)

這些成對比較應用於所有可行函式。如果只有一個可行函式優於所有其他函式,則過載決議成功並呼叫此函式。否則,編譯失敗。

void Fcn(const int*, short); // overload #1
void Fcn(int*, int);         // overload #2
 
int i;
short s = 0;
 
void f() 
{
    Fcn(&i, 1L);  // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: 1L -> short and 1L -> int are equivalent
                  // calls Fcn(int*, int)
 
    Fcn(&i, 'c'); // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: 'c' -> int is better than 'c' -> short
                  // calls Fcn(int*, int)
 
    Fcn(&i, s);   // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: s -> short is better than s -> int
                  // no winner, compilation error
}

如果最佳可行函式解析為一個找到多個宣告的函式,並且這些宣告中的任意兩個位於不同的作用域並指定了使函式可行的預設引數,則程式格式錯誤。

namespace A
{
    extern "C" void f(int = 5);
}
 
namespace B
{
    extern "C" void f(int = 5);
}
 
using A::f;
using B::f;
 
void use()
{
    f(3); // OK, default argument was not used for viability
    f();  // error: found default argument twice
}

[編輯] 隱式轉換序列的排序

過載決議考慮的實參-形參隱式轉換序列對應於複製初始化中使用的隱式轉換(對於非引用形參),除了在考慮轉換為隱式物件引數或賦值運算子的左側時,不考慮建立臨時物件的轉換。當形參是靜態成員函式的隱式物件引數時,隱式轉換序列是一個標準轉換序列,它既不比任何其他標準轉換序列更好也不更差。(C++23 起)

每種標準轉換序列型別都被賦予三個等級之一:

1) 精確匹配:不需要轉換、左值到右值轉換、資格轉換、函式指標轉換、(C++17 起)、類型別到相同類的使用者定義轉換。
2) 提升:整型提升、浮點提升。
3) 轉換:整型轉換、浮點轉換、浮點-整型轉換、指標轉換、成員指標轉換、布林轉換、派生類到其基類的使用者定義轉換。

標準轉換序列的等級是它所包含的標準轉換中等級最差的(最多可能包含三次轉換)。

將引用形參直接繫結到實參表示式要麼是同一性轉換,要麼是派生到基的轉換。

struct Base {};
struct Derived : Base {} d;
 
int f(Base&);    // overload #1
int f(Derived&); // overload #2
 
int i = f(d); // d -> Derived& has rank Exact Match
              // d -> Base& has rank Conversion
              // calls f(Derived&)

由於轉換序列的排序僅作用於型別和值類別,因此位域可以為了排序而繫結到引用實參,但如果該函式被選中,則它是格式錯誤的。

1) 標準轉換序列總是*優於*使用者定義轉換序列或省略號轉換序列。
2) 使用者定義轉換序列總是*優於*省略號轉換序列。
3) 如果滿足以下任一條件,則標準轉換序列 S1 *優於*標準轉換序列 S2
a) S1S2 的真子序列,排除左值轉換;同一性轉換序列被視為任何非同一性轉換的子序列,或者,如果不是這樣,
b) S1 的等級優於 S2 的等級,或者,如果不是這樣,
c) S1S2 都繫結到除引用限定成員函式的隱式物件引數之外的引用引數,並且 S1 將右值引用繫結到右值,而 S2 將左值引用繫結到右值,
int i;
int f1();
 
int g(const int&);  // overload #1
int g(const int&&); // overload #2
 
int j = g(i);    // lvalue int -> const int& is the only valid conversion
int k = g(f1()); // rvalue int -> const int&& better than rvalue int -> const int&
或者,如果不是這樣,
d) S1S2 都繫結到引用引數,並且 S1 將左值引用繫結到函式,而 S2 將右值引用繫結到函式。
int f(void(&)());  // overload #1
int f(void(&&)()); // overload #2
 
void g();
int i1 = f(g); // calls #1
或者,如果不是這樣,
e) S1S2 僅在資格轉換方面不同,並且

S1 結果的 cv-限定是 S2 結果的 cv-限定的真子集,並且 S1 不是已棄用的字串字面量陣列到指標轉換(C++11 前)

(C++20 前)

S1 的結果可以透過資格轉換轉換為 S2 的結果。

(C++20 起)
int f(const int*);
int f(int*);
 
int i;
int j = f(&i); // &i -> int* is better than &i -> const int*, calls f(int*)
或者,如果不是這樣,
f) S1S2 都繫結到僅在頂層 cv-限定方面不同的引用引數,並且 S1 的型別 cv-限定*小於* S2 的型別。
int f(const int &); // overload #1
int f(int &);       // overload #2 (both references)
 
int g(const int &); // overload #1
int g(int);         // overload #2
 
int i;
int j = f(i); // lvalue i -> int& is better than lvalue int -> const int&
              // calls f(int&)
int k = g(i); // lvalue i -> const int& ranks Exact Match
              // lvalue i -> rvalue int ranks Exact Match
              // ambiguous overload: compilation error
或者,如果不是這樣,
g) S1S2 繫結相同的引用型別“對 T 的引用”,並且分別具有源型別 V1V2,其中從 V1*T* 的標準轉換序列優於從 V2*T* 的標準轉換序列。
struct Z {};
 
struct A
{
    operator Z&();
    operator const Z&();  // overload #1
};
 
struct B
{
    operator Z();
    operator const Z&&(); // overload #2
};
 
const Z& r1 = A();        // OK, uses #1
const Z&& r2 = B();       // OK, uses #2
4) 如果使用者定義轉換序列 U1U2 呼叫相同的建構函式/使用者定義轉換函式或使用聚合初始化初始化相同的類,並且在任一情況下 U1 中的第二個標準轉換序列優於 U2 中的第二個標準轉換序列,則 U1 *優於* U2
struct A
{
    operator short(); // user-defined conversion function
} a;
 
int f(int);   // overload #1
int f(float); // overload #2
 
int i = f(a); // A -> short, followed by short -> int (rank Promotion)
              // A -> short, followed by short -> float (rank Conversion)
              // calls f(int)
5) 如果列表初始化序列 L1 初始化一個 std::initializer_list 引數,而 L2 不初始化,則 L1 *優於* L2
void f1(int);                                 // #1
void f1(std::initializer_list<long>);         // #2
void g1() { f1({42}); }                       // chooses #2
 
void f2(std::pair<const char*, const char*>); // #3
void f2(std::initializer_list<std::string>);  // #4
void g2() { f2({"foo", "bar"}); }             // chooses #4
6) 如果對應的引數是對陣列的引用,並且 L1 轉換為“N1 T 陣列”型別,L2 轉換為“N2 T 陣列”型別,且 N1 小於 N2,則列表初始化序列 L1 *優於* L2
(C++11 起)
(C++20 前)
6) 如果對應的引數是對陣列的引用,並且 L1L2 轉換為相同元素型別的陣列,並且滿足以下任一條件,則列表初始化序列 L1 *優於* L2
  • L1 初始化的元素數量 N1 小於 L2 初始化的元素數量 N2,或者
  • N1 等於 N2 且 L2 轉換為未知邊界的陣列而 L1 不轉換。
void f(int    (&&)[] ); // overload #1
void f(double (&&)[] ); // overload #2
void f(int    (&&)[2]); // overload #3
 
f({1});        // #1: Better than #2 due to conversion, better than #3 due to bounds
f({1.0});      // #2: double -> double is better than double -> int
f({1.0, 2.0}); // #2: double -> double is better than double -> int
f({1, 2});     // #3: -> int[2] is better than -> int[], 
               //     and int -> int is better than int -> double
(C++20 起)

如果兩個轉換序列因具有相同等級而無法區分,則適用以下附加規則:

1) 不涉及指標到 bool 或成員指標到 bool 的轉換優於涉及這些轉換的轉換。
2) 如果兩種型別不同,將基礎型別固定的列舉提升到其基礎型別的轉換優於提升到提升後的基礎型別的轉換。
enum num : char { one = '0' };
std::cout << num::one; // '0', not 48
(C++11 起)


3) 如果滿足以下任一條件,浮點型別 FP1 和浮點型別 FP2 之間雙向轉換優於 FP1 和算術型別 T3 之間相同方向的轉換:
  • FP1浮點轉換等級等於 FP2 的等級,並且
    • T3 不是浮點型別,或者
    • T3 是浮點型別,其等級不等於 FP1 的等級,或者
    • FP2浮點轉換子等級大於 T3 的子等級。
int f(std::float32_t);
int f(std::float64_t);
int f(long long);
 
float x;
std::float16_t y;
 
int i = f(x); // calls f(std::float32_t) on implementations where
              // float and std::float32_t have equal conversion ranks
int j = f(y); // error: ambiguous, no equal conversion rank
(C++23 起)
4) 派生類指標到基類指標的轉換優於派生類指標到 void 指標的轉換,基類指標到 void 的轉換優於派生類指標到 void 的轉換。
5) 如果 Mid 直接或間接派生自 Base,並且 Derived 直接或間接派生自 Mid
a) Derived*Mid* 優於 Derived*Base*
b) DerivedMid&Mid&& 優於 DerivedBase&Base&&
c) Base::*Mid::* 優於 Base::*Derived::*
d) DerivedMid 優於 DerivedBase
e) Mid*Base* 優於 Derived*Base*
f) MidBase&Base&& 優於 DerivedBase&Base&&
g) Mid::*Derived::* 優於 Base::*Derived::*
h) MidBase 優於 DerivedBase

模糊轉換序列被排序為使用者定義轉換序列,因為一個引數的多個轉換序列只能在涉及不同的使用者定義轉換時存在。

class B;
 
class A { A (B&);};         // converting constructor
class B { operator A (); }; // user-defined conversion function
class C { C (B&); };        // converting constructor
 
void f(A) {} // overload #1
void f(C) {} // overload #2
 
B b;
f(b); // B -> A via ctor or B -> A via function (ambiguous conversion)
      // b -> C via ctor (user-defined conversion)
      // the conversions for overload #1 and for overload #2
      // are indistinguishable; compilation fails

[編輯] 列表初始化中的隱式轉換序列

列表初始化中,實參是一個 braced-init-list,它不是一個表示式,因此用於過載決議的隱式轉換序列到形參型別由以下特殊規則決定:

  • 如果形參型別是某個聚合 X 且初始化列表恰好包含一個相同或派生類的元素(可能 cv-限定),則隱式轉換序列是將該元素轉換為形參型別所需的轉換序列。
  • 否則,如果形參型別是對字元陣列的引用且初始化列表包含一個適當型別的字串字面量元素,則隱式轉換序列是同一性轉換。
  • 否則,如果形參型別是 std::initializer_list<X>,並且從初始化列表的每個元素到 X 都有一個非收窄隱式轉換,則用於過載決議的隱式轉換序列是所需的最差轉換。如果大括號初始化列表為空,則轉換序列是同一性轉換。
struct A
{
    A(std::initializer_list<double>);          // #1
    A(std::initializer_list<complex<double>>); // #2
    A(std::initializer_list<std::string>);     // #3
};
A a{1.0, 2.0};     // selects #1 (rvalue double -> double: identity conv)
 
void g(A);
g({"foo", "bar"}); // selects #3 (lvalue const char[4] -> std::string: user-def conv)
  • 否則,如果形參型別是“N 個 T 陣列”(這僅適用於對陣列的引用),則初始化列表必須有 N 個或更少元素,並且將列表的每個元素(或如果列表短於 N 則為空大括號 {})轉換為 T 所需的最差隱式轉換將被使用。
  • 否則,如果形參型別是“未知邊界的 T 陣列”(這僅適用於對陣列的引用),則將列表的每個元素轉換為 T 所需的最差隱式轉換將被使用。
(C++20 起)
typedef int IA[3];
 
void h(const IA&);
void g(int (&&)[]);
 
h({1, 2, 3}); // int->int identity conversion
g({1, 2, 3}); // same as above since C++20
  • 否則,如果形參型別是非聚合類型別 X,過載決議選擇 X 的建構函式 C 從實參初始化列表進行初始化:
  • 如果 C 不是初始化列表建構函式且初始化列表包含一個可能 cv-限定的 X 型別的單個元素,則隱式轉換序列具有精確匹配等級。如果初始化列表包含一個可能 cv-限定的從 X 派生型別的單個元素,則隱式轉換序列具有轉換等級。(請注意與聚合的區別:聚合在考慮聚合初始化之前直接從單元素初始化列表進行初始化,而非聚合則首先考慮初始化列表建構函式,然後考慮其他任何建構函式)。
  • 否則,隱式轉換序列是使用者定義轉換序列,第二個標準轉換序列是同一性轉換。

如果多個建構函式可行但沒有一個優於其他,則隱式轉換序列是模糊轉換序列。

struct A { A(std::initializer_list<int>); };
void f(A);
 
struct B { B(int, double); };
void g(B);
 
g({'a', 'b'});    // calls g(B(int, double)), user-defined conversion
// g({1.0, 1,0}); // error: double->int is narrowing, not allowed in list-init
 
void f(B);
// f({'a', 'b'}); // f(A) and f(B) both user-defined conversions
  • 否則,如果形參是可根據聚合初始化規則從初始化列表初始化的聚合,則隱式轉換序列是使用者定義轉換序列,其第二個標準轉換序列是同一性轉換。
struct A { int m1; double m2; };
 
void f(A);
f({'a', 'b'}); // calls f(A(int, double)), user-defined conversion
  • 否則,如果形參是引用,則應用引用初始化規則。
struct A { int m1; double m2; };
 
void f(const A&);
f({'a', 'b'}); // temporary created, f(A(int, double)) called. User-defined conversion
  • 否則,如果形參型別不是類且初始化列表有一個元素,則隱式轉換序列是將該元素轉換為形參型別所需的轉換序列。
  • 否則,如果形參型別不是類型別且初始化列表沒有元素,則隱式轉換序列是同一性轉換。

如果實參是指定初始化列表且形參不是引用,則只有當形參具有可根據聚合初始化規則從該初始化列表初始化的聚合型別時,才可能進行轉換,在這種情況下,隱式轉換序列是使用者定義轉換序列,其第二個標準轉換序列是同一性轉換。

如果在過載決議後,聚合成員的宣告順序與選定過載不匹配,則形參的初始化將格式錯誤。

struct A { int x, y; };
struct B { int y, x; };
 
void f(A a, int); // #1
void f(B b, ...); // #2
void g(A a);      // #3
void g(B b);      // #4
 
void h() 
{
    f({.x = 1, .y = 2}, 0); // OK; calls #1
    f({.y = 2, .x = 1}, 0); // error: selects #1, initialization of a fails
                            // due to non-matching member order
    g({.x = 1, .y = 2});    // error: ambiguous between #3 and #4
}


(C++20 起)

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 1 C++98 當選擇具有可能不同預設
引數(來自不同作用域)的同一函式時,行為未指定。
預設引數(來自不同作用域)被選中時
程式格式錯誤
在這種情況下,列舉是病態的
CWG 83 C++98 字串字面量到 char* 的轉換序列優於到 const char* 的轉換序列
char* 的轉換序列優於到 const char* 的轉換序列
即使前者已棄用
棄用轉換的等級降低(它
在 C++11 中被移除)
在 C++11 中被移除)
CWG 162 C++98 如果由 F 命名的過載集包含一個非靜態成員函式,在 &F(args) 的情況下,它是無效的
&F(args) 的情況下包含非靜態成員函式,這是無效的。
僅在過載決議選擇非靜態成員函式的情況下無效
僅在過載
決議選擇非靜態
成員函式時才無效 C++98 CWG 233
在使用使用者定義轉換進行過載決議時,引用和指標的處理不一致
它們得到一致處理
得到一致處理
CWG 280 C++98 代理呼叫函式未新增到候選函式集合中,用於在不可訪問基類中宣告的轉換函式。
用於轉換的候選函式集合
在不可訪問基類中宣告的函式
移除了可訪問性
約束,如果選擇了代理呼叫函式,並且相應的轉換函式無法呼叫,則程式格式錯誤。
格式錯誤
函式被選中且對應
轉換函式無法呼叫
轉換函式無法呼叫
CWG 415 C++98 當函式模板被選為候選時,其特化會使用模板引數推導進行例項化。
其特化會使用模板引數推導進行例項化
使用模板引數推導
在這種情況下不會發生例項化,它們的宣告將被合成。
在這種情況下,它們的宣告
將被合成
CWG 495 C++98 當引數的隱式轉換同樣好時,非模板轉換函式總是優於轉換函式模板,即使後者可能有更好的標準轉換序列。
好的,非模板轉換函式總是
優於轉換函式模板,即使
後者可能具有更好的標準轉換序列
標準轉換
序列在特化級別之前進行比較
序列在特化級別之前進行比較
CWG 1307 C++11 基於陣列大小的過載決議未指定 在可能的情況下,較短的陣列是更好的。
在可能的情況下更好
CWG 1328 C++11 在將引用繫結到轉換結果時,候選函式的確定不明確。
繫結到轉換結果時,候選函式的確定不明確
已明確
CWG 1374 C++98 在比較標準轉換序列時,資格轉換在引用繫結之前檢查。
繫結時,資格轉換在引用繫結之前檢查。
顛倒了
CWG 1385 C++11 使用 ref-qualifier 宣告的非顯式使用者定義轉換函式沒有相應的代理函式。
一個 ref-qualifier 沒有相應的代理函式
它有一個相應的代理函式。
代理函式
CWG 1467 C++11 聚合和陣列的同類型列表初始化被省略
初始化已定義
初始化已定義
CWG 1601 C++11 從列舉到其底層型別的轉換
沒有優先選擇固定的底層型別
固定型別優先於其提升型別
到它提升到的型別
CWG 1608 C++98 一元運算子 @ 的成員候選集為空,如果其引數型別為 T1T1 是當前正在定義的類。
如果其引數型別為 T1 為空,則其成員候選集為空。
T1 是當前正在定義的類
在這種情況下,該集合是 T1::operator@ 限定名查詢的結果。
在這種情況下,T1::operator@
的限定名查詢結果
CWG 1687 C++98 當過載決議選擇內建候選時,運算元將不受限制地進行轉換。
運算元將不受限制地進行轉換
只轉換類型別運算元,並停用第二個標準轉換序列。
並停用了第二個
標準轉換序列
CWG 2052 C++98 格式錯誤的合成函式模板特化可能會被新增到候選集中,從而導致程式格式錯誤。
新增到候選集中,導致程式格式錯誤
它們不會新增到候選集中
新增到候選集中
CWG 2076 C++11 在列表初始化期間,使用者定義轉換應用於巢狀初始化列表中的單個初始化器。
在列表初始化期間,使用者定義轉換應用於巢狀初始化列表中的單個初始化器。
由於 CWG 問題 1467 的解決
未應用
CWG 2137 C++11 當從 {X} 列表初始化 X 時,初始化列表建構函式輸給了複製建構函式。
當從 {X} 列表初始化 X
非聚合首先考慮初始化列表
首先考慮初始化列表
CWG 2273 C++11 在繼承和非繼承建構函式之間沒有決勝規則
在繼承和非繼承建構函式之間沒有決勝規則
非繼承建構函式獲勝
CWG 2673 C++20 與重寫非成員候選具有相同引數列表的內建候選被新增到內建候選列表。
列表作為重寫非成員候選
被新增到內建候選列表
未新增
CWG 2712 C++98 當考慮內建賦值運算子時,第一個引數無法繫結到臨時物件,這已經是不可能的了[1]
第一個引數無法繫結到臨時物件,這已經是不可能的了
臨時物件,這已經是不可能的[1]
移除了冗餘要求
冗餘要求
CWG 2713 C++20 即使引數是引用,也應用了關於指定初始化列表的轉換限制。
列表被應用,即使引數是引用。
在這種情況下不受限制
CWG 2789 C++23 在比較引數型別列表時,顯式物件引數被包含在內
在比較引數型別列表時
已排除
CWG 2856 C++11 在複製列表初始化的上下文中,預設初始化的過載決議僅考慮轉換建構函式。
複製列表初始化的上下文中,預設初始化的過載決議僅考慮轉換建構函式。
考慮所有建構函式
CWG 2919 C++98 透過轉換進行引用初始化的候選集取決於初始化的目標型別。
取決於初始化的目標型別
取決於轉換的目標型別
轉換的目標型別
P2468R2 C++20 基於 operator== 的重寫候選即使存在匹配的 operator!=,也會新增到 a != b 中。
對於 a != b,即使存在匹配的 operator!=
未新增
  1. 內建賦值運算子的第一個引數型別是“對可能 volatile-qualified T 的左值引用”。這種型別的引用不能繫結到臨時物件。

[編輯] 參考文獻

  • C++23 標準 (ISO/IEC 14882:2024)
  • 12.2 過載決議 [over.match]
  • C++20 標準 (ISO/IEC 14882:2020)
  • 12.4 過載決議 [over.match]
  • C++17 標準 (ISO/IEC 14882:2017)
  • 16.3 過載決議 [over.match]
  • C++14 標準 (ISO/IEC 14882:2014)
  • 13.3 過載決議 [over.match]
  • C++11 標準 (ISO/IEC 14882:2011)
  • 13.3 過載決議 [over.match]
  • C++03 標準 (ISO/IEC 14882:2003)
  • 13.3 過載決議 [over.match]

[編輯] 另見