重载决议
为了编译函数调用,编译器必须首先执行名字查找,对于函数,这可能涉及到实参依赖查找,而对于函数模板,之后可能是模板实参推导。
如果名字指代多于一个实体,则称之为重载,并且编译器必须确定要调用哪个重载。简单来说,参数与实参最匹配的重载版本会被调用。
详细来说,重载决议通过以下步骤进行
void f(long); void f(float); f(0L); // calls f(long) f(0); // error: ambiguous overload
除了函数调用,重载函数名可能出现在几个额外的上下文中,在这些上下文中应用不同的规则:参见重载函数的地址。
如果一个函数无法通过重载决议选择,则它不能被使用(例如,它是一个具有失败约束的模板实体)。
内容 |
[编辑] 候选函数
在重载决议开始之前,通过名字查找和模板实参推导选择的函数被组合起来,形成候选函数的集合。确切的细节取决于重载决议将要发生的上下文。
[编辑] 对具名函数的调用
如果在函数调用表达式 E(args) 中,E 命名了一组重载函数和/或函数模板(但不是可调用对象),则遵循以下规则
- 如果表达式 E 的形式为 PA->B 或 A.B (其中 A 具有类类型 cv
T
),则 B 会作为T
的成员函数被查找。通过该查找找到的函数声明是候选函数。为了重载决议的目的,实参列表具有类型为 cvT
的隐式对象实参。 - 如果表达式 E 是一个主表达式,则根据函数调用的正常规则查找该名称(可能涉及到ADL)。通过此查找找到的函数声明(由于查找的工作方式)要么是
- 所有非成员函数(在这种情况下,为了重载决议的目的,实参列表与函数调用表达式中使用的实参列表完全相同)
- 某些类
T
的所有成员函数,在这种情况下,如果 this 在作用域内并且是指向T
或T
的派生类的指针,则 *this 用作隐式对象实参。否则(如果 this 不在作用域内或不指向T
),则将类型为T
的虚假对象用作隐式对象实参,并且如果重载决议随后选择了非静态成员函数,则程序是非良构的。
[编辑] 对类对象的调用
如果在函数调用表达式 E(args) 中,E 具有类类型 cv T
,则
T
的函数调用运算符通过在表达式 (E).operator() 的上下文中对名称 operator() 进行普通查找获得,并且找到的每个声明都被添加到候选函数集合中。- 对于
T
或T
的基类中(除非被隐藏)的每个非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 限定符后),准备以下候选函数集合
operator@
进行非限定名查找找到的所有声明(可能涉及到ADL),但成员函数声明会被忽略,并且不会阻止查找继续进入下一个外围作用域。如果二元运算符的两个操作数或一元运算符的唯一操作数具有枚举类型,则来自查找集合的唯一成为非成员候选的函数是那些形参具有该枚举类型(或对该枚举类型的引用)的函数
4) 重写候选
在所有情况下,重写候选都不会在重写表达式的上下文中考虑。对于所有其他运算符,重写候选集合为空。 |
(自 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<=>(std::string, const char*) 和 operator==(std::string, const char*) 即可生成 std::string 和 const char* 之间所有方向的比较。有关更多详细信息,请参见默认比较。 |
(自 C++20 起) |
[编辑] 通过构造函数初始化
当类类型的对象被直接初始化或默认初始化(包括复制列表初始化上下文中的默认初始化)(自 C++11 起)时,候选函数是正在初始化的类的所有构造函数。实参列表是初始化器的表达式列表。
否则,候选函数是正在初始化的类的所有转换构造函数。实参列表是初始化器的表达式。
对于复制列表初始化上下文中的默认初始化,如果选择了 |
(自 C++11 起) |
[编辑] 通过转换进行复制初始化
如果类类型对象的复制初始化需要调用用户定义的转换来将类型为 cv S
的初始化器表达式转换为正在初始化的对象的类型 cv T
,则以下函数是候选函数
T
的所有转换构造函数- 从
S
及其基类(除非被隐藏)到T
或从T
派生的类或对这种类型的引用的非explicit
转换函数。如果此复制初始化是 cvT
的直接初始化序列的一部分(初始化要绑定到构造函数的第一个形参的引用,该构造函数采用对 cvT
的引用),则也会考虑显式转换函数。
无论哪种方式,为了重载决议的目的,实参列表都由一个实参组成,该实参是初始化器表达式,它将与构造函数的第一个实参或转换函数的隐式对象实参进行比较。
[编辑] 通过转换进行非类初始化
当非类类型 cv1 T
的对象的初始化需要用户定义的转换函数以从类类型 cv S
的初始化器表达式进行转换时,以下函数是候选函数
S
及其基类(除非被隐藏)的非显式用户定义的转换函数,这些函数生成类型T
或可通过标准转换序列转换为T
的类型,或对此类类型的引用。返回类型上的 cv 限定符在选择候选函数时被忽略。- 如果是直接初始化,则也会考虑
S
及其基类(除非被隐藏)的显式用户定义的转换函数,这些函数生成类型T
或可通过限定转换转换为T
的类型,或对此类类型的引用。
无论哪种方式,为了重载决议的目的,实参列表都由一个实参组成,该实参是初始化器表达式,它将与转换函数的隐式对象实参进行比较。
[编辑] 通过转换进行引用初始化
在引用初始化期间,当对 cv1 T
的引用绑定到从类类型 cv2 S
的初始化器表达式转换而来的左值或右值结果时,选择以下函数作为候选集合
S
及其基类(除非被隐藏)的非显式用户定义的转换函数,转换为类型
- (当转换为左值时)对 cv2
T2
的左值引用 - (当转换为右值或函数类型的左值时)cv2
T2
或对 cv2T2
的右值引用
- (当转换为左值时)对 cv2
- 其中 cv2
T2
与 cv1T
引用兼容。
- 对于直接初始化,如果
T2
与T
的类型相同,或者可以通过限定转换转换为类型T
,则也会考虑显式用户定义的转换函数。
无论哪种方式,为了重载决议的目的,实参列表都由一个实参组成,该实参是初始化器表达式,它将与转换函数的隐式对象实参进行比较。
[编辑] 列表初始化
当非聚合类类型 T
的对象被列表初始化时,会发生两阶段重载决议。
- 在阶段 1,候选函数是
T
的所有初始化器列表构造函数,并且为了重载决议的目的,实参列表由单个初始化器列表实参组成 - 如果在阶段 1 重载决议失败,则进入阶段 2,其中候选函数是
T
的所有构造函数,并且为了重载决议的目的,实参列表由初始化器列表的各个元素组成。
如果初始化器列表为空且 T
具有默认构造函数,则跳过阶段 1。
在复制列表初始化中,如果阶段 2 选择了一个显式构造函数,则初始化是非良构的(与所有复制初始化相反,在复制初始化中,甚至不考虑显式构造函数)。
[编辑] 函数模板候选的附加规则
如果名字查找找到了一个函数模板,则会执行模板实参推导和任何显式模板实参的检查,以找到可以在这种情况下使用的模板实参值(如果有的话)
- 如果两者都成功,则模板实参用于合成相应函数模板特化的声明,这些声明被添加到候选集合中,并且这些特化被视为就像非模板函数一样,除非在下面的决胜规则中另有规定。
- 如果实参推导失败或合成的函数模板特化将是非良构的,则不会将此类函数添加到候选集合中(参见SFINAE)。
如果一个名字引用一个或多个函数模板,并且还引用一组重载的非模板函数,则这些函数和从模板生成的特化都是候选。
有关更多详细信息,请参见函数模板重载。
如果构造函数模板或转换函数模板具有碰巧是值依赖的条件显式说明符,则在推导之后,如果上下文需要非显式候选并且生成的特化是显式的,则会将其从候选集合中移除。 |
(自 C++20 起) |
[编辑] 构造函数候选的附加规则
被定义为已删除的默认 移动构造函数 和 移动赋值运算符 会从候选函数集中排除。 从类类型
|
(自 C++11 起) |
[编辑] 成员函数候选的附加规则
如果任何候选函数是成员函数(静态或非静态),且没有显式对象参数(自 C++23 起),但不是构造函数,则将其视为具有一个额外的参数(隐式对象参数),该参数表示调用它们的对象,并出现在第一个实际参数之前。
类似地,调用成员函数的对象会作为隐式对象实参添加到实参列表的前面。
对于类 X
的成员函数,隐式对象参数的类型受成员函数的 cv 限定符和 ref 限定符的影响,如成员函数中所述。
为了确定隐式对象参数的类型,用户定义的转换函数被认为是隐式对象实参的成员。
通过 using 声明引入派生类的成员函数被认为是派生类的成员,用于定义隐式对象参数的类型。
对于静态成员函数,隐式对象参数被认为与任何对象匹配:其类型不被检查,并且不为其尝试转换序列。 |
(直到 C++23) |
对于重载解析的其余部分,隐式对象实参与其他实参没有区别,但以下特殊规则适用于隐式对象参数:
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
[编辑] 可行函数
给定如上所述构建的候选函数集,重载解析的下一步是检查实参和形参,以将集合缩减为可行函数集。
要包含在可行函数集中,候选函数必须满足以下条件:
M
个实参,则正好有 M
个形参的候选函数是可行的。M
个形参,并且第 M+1
个形参以及之后的所有形参都具有默认实参,则它是可行的。对于重载解析的其余部分,形参列表在 M
处被截断。
4) 如果函数具有关联的约束,则必须满足该约束。
|
(自 C++20 起) |
用户定义的转换(包括转换构造函数和用户定义的转换函数)被禁止参与隐式转换序列,如果它可能导致应用多个用户定义的转换。具体来说,如果转换的目标是构造函数的第一个形参或用户定义的转换函数的隐式对象参数,并且该构造函数/用户定义的转换是以下情况的候选者,则不考虑它们:
- 通过用户定义的转换进行类的复制初始化,
- 通过转换函数初始化非类类型,
- 通过转换函数进行直接引用绑定的初始化,
- 通过构造函数初始化,在类复制初始化的第二个(直接初始化)步骤期间,
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 起) |
[编辑] 最佳可行函数
对于每对可行函数 F1
和 F2
,从第 i
个实参到第 i
个形参的隐式转换序列进行排序,以确定哪个更好(除了第一个实参,静态成员函数的隐式对象实参对排序没有影响)
如果 F1
的所有实参的隐式转换不比 F2 的所有实参的隐式转换差,则确定 F1
比 F2
更好,并且
F1
至少有一个实参的隐式转换优于 F2
的该实参的相应隐式转换;或者,如果不是这样,F1
的结果到要初始化的类型的标准转换序列优于从 F2
的结果到要初始化的类型的标准转换序列;或者,如果不是这样,
3) (仅在通过转换函数进行函数类型引用的直接引用绑定的上下文中),
F1 的结果与要初始化的引用类型(左值或右值)相同,而 F2 的结果不是;或者,如果不是这样, |
(自 C++11 起) |
F1
是非模板函数,而 F2
是模板特化;或者,如果不是这样,
6)
F1 和 F2 都是非模板函数,并且 F1 比 F2 更偏序约束。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 的构造函数,F2 是 D 的基类 B 的构造函数,并且对于所有实参,F1 和 F2 的对应形参具有相同的类型struct A { A(int = 0); }; struct B: A { using A::A; B(); }; B b; // OK, B::B() 或者,如果不是这样,
|
(自 C++11 起) |
8)
F2 是重写的候选函数,而 F1 不是;或者,如果不是这样,9)
F1 和 F2 都是重写的候选函数,并且 F2 是参数顺序相反的合成重写候选函数,而 F1 不是;或者,如果不是这样, |
(自 C++20 起) |
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 起)
每个标准转换序列类型被分配三个等级之一:
标准转换序列的等级是它所包含的标准转换的最差等级(可能最多有三个转换)
将引用形参直接绑定到实参表达式要么是同一性,要么是派生类到基类的转换
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&)
由于转换序列的排序仅使用类型和值类别,因此位域可以绑定到引用实参以进行排序,但如果选择了该函数,则它是非良构的。
S1
优于 标准转换序列 S2
,如果S1
是 S2
的真子序列,排除左值转换;同一性转换序列被认为是任何非同一性转换的子序列;或者,如果不是这样,S1
的等级优于 S2
的等级;或者,如果不是这样,S1
和 S2
都绑定到引用形参,而不是 ref 限定成员函数的隐式对象参数,并且 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&
S1
和 S2
都绑定到引用形参,并且 S1
将左值引用绑定到函数,而 S2
将右值引用绑定到函数int f(void(&)()); // overload #1 int f(void(&&)()); // overload #2 void g(); int i1 = f(g); // calls #1
S1
和 S2
仅在限定转换方面有所不同,并且
|
(直到 C++20) |
|
(自 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*)
S1
和 S2
绑定到仅在顶层 cv 限定方面不同的引用形参,并且 S1
的类型比 S2
的类型 cv 限定更少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
S1
和 S2
绑定相同的引用类型 “T
的引用”,并且分别具有源类型 V1
和 V2
,其中从 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
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)
L1
优于 列表初始化序列 L2
,如果 L1
初始化 std::initializer_list 形参,而 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 优于 列表初始化序列 L2 ,如果对应的形参是对数组的引用,并且 L1 转换为 “N1 个 T 的数组” 类型,L2 转换为 “N2 个 T 的数组” 类型,并且 N1 小于 N2。 |
(自 C++11 起) (直到 C++20) |
6) 列表初始化序列 L1 优于 列表初始化序列 L2 ,如果对应的形参是对数组的引用,并且 L1 和 L2 转换为相同元素类型的数组,并且满足以下任一条件:
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 起) |
如果两个转换序列因具有相同的等级而无法区分,则适用以下附加规则:
(自 C++11 起) |
3) 浮点类型
FP1 和浮点类型 FP2 之间任一方向的转换,优于 FP1 和算术类型 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 起) |
Mid
从 Base
派生(直接或间接),并且 Derived
从 Mid
派生(直接或间接)Derived*
到 Mid*
优于 Derived*
到 Base*
Derived
到 Mid&
或 Mid&&
优于 Derived
到 Base&
或 Base&&
Base::*
到 Mid::*
优于 Base::*
到 Derived::*
Derived
到 Mid
优于 Derived
到 Base
Mid*
到 Base*
优于 Derived*
到 Base*
Mid
到 Base&
或 Base&&
优于 Derived
到 Base&
或 Base&&
Mid::*
到 Derived::*
优于 Base::*
到 Derived::*
Mid
到 Base
优于 Derived
到 Base
歧义转换序列被评为用户定义的转换序列,因为对于一个实参,只有当它们涉及不同的用户定义的转换时,才可能存在多个转换序列。
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
[编辑] 列表初始化中的隐式转换序列
在列表初始化中,实参是 花括号初始化列表,它不是表达式,因此用于重载解析的到形参类型的隐式转换序列由以下特殊规则决定:
- 如果形参类型是某个聚合
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
所需的最差隐式转换是使用的转换。
|
(自 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,则隐式转换序列具有精确匹配等级。如果初始化列表具有单个元素,该元素可能是从 X 派生的 cv 限定类型,则隐式转换序列具有转换等级。(请注意与聚合的区别:聚合在考虑聚合初始化之前,直接从单元素初始化列表初始化;非聚合在任何其他构造函数之前考虑 initializer_list 构造函数)
- 否则,隐式转换序列是用户定义的转换序列,其中第二个标准转换序列是同一性转换。
如果多个构造函数是可行的,但没有一个优于其他构造函数,则隐式转换序列是歧义转换序列。
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++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 1 | C++98 | 当相同的函数具有可能不同的默认实参(来自不同作用域)被选择时,行为未指定 function with possibly different default arguments (from different scopes) is selected |
程序在这种情况下是非良构的 formed in this case |
CWG 83 | C++98 | 从字符串字面量到 char* 的转换序列优于到 const char* 的转换序列,即使前者已弃用 to char* was better than that to const char* even though the former is deprecated |
已弃用转换的等级降低(在 C++11 中已删除) conversion is lowered (it was removed in C++11) |
CWG 162 | C++98 | 在 &F(args) 的情况下,如果 F 命名的重载集包含非静态成员函数,则它是无效的a non-static member function in the case of &F(args) |
仅当重载解析在这种情况下选择非静态成员函数时才无效 resolution selects a non-static member function in this case |
CWG 233 | C++98 | 在使用用户定义的转换进行重载解析时,引用和指针的处理不一致 overloading resolution with user defined conversions |
它们被一致地处理 consistently |
CWG 280 | C++98 | 代理调用函数未添加到在不可访问的基类中声明的转换函数的候选函数集中 the set of candidate functions for conversion functions declared in inaccessible base classes |
删除了可访问性约束,如果选择了代理调用函数,并且无法调用相应的转换函数,则程序是非良构的 constraint, the program is ill-formed if a surrogate call function is selected and the corresponding conversion function cannot be called |
CWG 415 | C++98 | 当选择函数模板作为候选函数时,使用模板实参推导实例化其特化 candidate, its specializations were instantiated using template argument deduction |
在这种情况下不会发生实例化,将合成它们的声明 in this case, their declarations will be synthesized |
CWG 495 | C++98 | 当实参的隐式转换同样好时,非模板转换函数始终优于转换函数模板,即使后者可能具有更好的标准转换序列 good, a non-template conversion function was always better than a conversion function template, even if the latter may have a better standard conversion sequence |
在特化级别之前比较标准转换序列 sequences are compared before specialization levels |
CWG 1307 | C++11 | 基于数组大小的重载解析未指定 | a shorter array is better when possible |
当可能时,较短的数组更好 | C++11 | CWG 1328 绑定到转换结果的引用时,候选函数的确定不明确 |
a reference to a conversion result was not clear |
已明确 | C++98 | CWG 1374 在比较标准转换序列时,在引用绑定之前检查限定转换 |
binding when comparing standard conversion sequences |
已反转 | C++11 | CWG 1385 使用 ref 限定符声明的非显式用户定义转换函数没有相应的代理函数 |
a ref-qualifier did not have a corresponding surrogate function 它具有相应的代理函数 |
CWG 1467 | C++11 | 省略了聚合和数组的相同类型列表初始化 of aggregates and arrays was omitted |
已定义初始化 |
CWG 1601 | C++11 | 从枚举到其底层类型的转换未优先选择固定的底层类型 did not prefer the fixed underlying type |
固定类型优先于其提升到的类型 to what it promotes to |
CWG 1608 | C++98 | 如果 T1 是当前正在定义的类,则一元运算符 @ 的实参类型为 T1 的成员候选集为空whose argument has type T1 was empty ifT1 is a class currently being defined |
该集合是这种情况下 T1::operator@ 的限定名称查找的结果qualified name lookup of T1::operator@ in this case |
CWG 1687 | C++98 | 当通过重载解析选择内置候选函数时,操作数将不受限制地进行转换 the operands would undergo conversion without restriction |
仅转换类类型操作数,并禁用第二个标准转换序列 and disabled the second standard conversion sequence |
CWG 2052 | C++98 | 非良构的合成函数模板特化可能会添加到候选集中,从而使程序成为非良构的 be added to the candidate set, making the program ill-formed |
它们未添加到候选集中 to the candidate set |
CWG 2076 | C++11 | 用户定义的转换应用于列表初始化期间嵌套初始化列表中的单个初始化器 in a nested initializer list during list-initialization 由于 CWG 问题 1467 的解决 |
未应用 |
CWG 2137 | C++11 | 当从 {X} 列表初始化 X 时,初始化列表构造函数输给了复制构造函数constructors when list-initializing X from {X} |
非聚合优先考虑初始化列表 initializer lists first |
CWG 2273 | C++11 | 继承的构造函数和非继承的构造函数之间没有决胜局 inherited and non-inherited constructors |
非继承的构造函数获胜 |
CWG 2673 | C++20 | 参数列表与重写的非成员候选函数相同的内置候选函数被添加到内置候选函数列表中 list as a rewritten non-member candidate were added to the list of built-in candidates |
未添加 |
CWG 2712 | C++98 | 当考虑内置赋值运算符时,第一个形参不能绑定到临时对象,这已经是不可行的[1] the first parameter could not bound to a temporary, which is already impossible[1] |
删除了冗余的要求 redundant requirement |
CWG 2713 | C++20 | 即使形参是对引用,关于指定初始化列表的转换限制也适用 lists was applied even if the parameter is a reference |
在这种情况下不受限制 |
CWG 2789 | C++23 | 在比较形参类型列表时,包括了显式对象参数 when comparing parameter-type-lists |
已排除 |
CWG 2856 | C++11 | 复制列表初始化上下文中默认初始化的重载解析仅考虑转换构造函数 of copy-list-initialization only considered converting constructor |
考虑所有构造函数 |
CWG 2919 | C++98 | 通过转换进行引用初始化的候选集取决于初始化的目标类型 depended on the target type of the initialization |
取决于转换的目标类型 type of the conversion |
P2468R2 | C++20 | 基于 operator== 重写的候选函数被添加到 a != b 中,即使存在匹配的 operator!= for a != b even if a matching operator!= exists |
未添加 |
- ↑ 内置赋值运算符的第一个形参的类型是 “可能带有 volatile 限定符的
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]