重载决议(函数匹配)
为了编译函数调用,编译器必须首先进行名字查找,对于函数可能涉及实参依赖查找,而对于函数模板可能后随模板实参推导。如果这些步骤产生了多个候选函数,那么需要进行重载决议选择将要实际调用的函数。
通常来说,所调用的函数是各形参与各实参之间的匹配最紧密的候选函数。
关于其他可以出现重载函数名的语境,见重载函数的地址。
如果函数无法被重载决议选择(例如它是有未被满足的约束的模板化实体),那么不能指名或再使用它。
细节
重载决议开始前,将名字查找和模板实参推导所选择的函数组成候选函数的集合(确切的判别标准取决于发生重载决议的语境,见下文)。
对于函数模板,为找到能在此时使用的模板实参值(如果存在)会进行模板实参推导和显式模板实参检查:
- 如果两者都成功,那么找到的模板实参会用来合成对应的函数模板特化的声明,那么这些特化会加入候选集,并且在决胜规则指定的情况以外都会被视为非模板函数;
- 如果模板实参推导失败或合成的函数模板特化非良构,那么这些函数不会加入候选集。
如果一个名字指代一个或多个函数模板,并且同时指代重载的非模板函数,那么这些函数和从模板生成的特化都是候选。
如果构造函数模板或转换函数模板拥有在推导后它恰好是值待决的条件性 explicit 说明符,且语境要求非 explicit 的候选而所生成的候选是 explicit 的,那么从候选集中移除它。 |
(C++20 起) |
(C++11 起) |
在构造派生类对象时,候选函数列表中不包含继承的复制和移动构造函数。
隐式对象形参
如果有候选函数是除构造函数外且没有显式对象形参 (C++23 起)的成员函数(静态或非静态),那么将它当做如同它有一个额外形参(隐式对象形参),代表调用函数所用的对象,并出现在首个实际形参之前。
类似地,调用成员函数所用的对象会作为隐含对象实参前附于实参列表。
对于类 X
的成员函数,隐含对象形参的类型受成员函数的 cv 限定和引用限定影响,如成员函数中所述。
就确定隐式对象形参类型而言,用户定义转换函数被认为是隐含对象实参的成员。
就确定隐式对象形参类型而言,由 using 声明引入到派生类中的成员函数被认为是派生类的成员。
对于静态成员函数,它的隐式对象形参被认为匹配任何对象:不检验它的类型,且不会为它尝试转换序列。
对于重载决议的剩余部分,隐含对象实参与其他实参不可辨别,但下列特殊规则适用于隐式对象形参:
struct B { void f(int); }; struct A { operator B&(); }; A a; a.B::f(1); // 错误:不能对隐式对象形参运用用户定义转换 static_cast<B&>(a).f(1); // OK
候选函数
使用重载决议的每种语境都以它独有的方式准备它的候选函数集合和实参列表:
调用具名函数
如果 E
在函数调用表达式 E(args)
中指名重载的函数和/或函数模板(但非可调用对象)的集合,那么遵循下列规则:
- 如果表达式
E
具有PA->B
或A.B
的形式(其中 A 具有类类型 cv T),那么将B
作为T
的成员函数查找。该查找所找到的函数声明都是候选函数。就重载决议而言,实参列表拥有 cv T 类型的隐含对象实参。 - 如果表达式
E
是初等表达式,那么遵循函数调用的正常规则查找它的名字(可能涉及 实参依赖查找)。该查找所找到的函数声明(取决于查找的工作方式)是下列之一:
- a) 全部是非成员函数(该情况下,就重载决议而言,实参列表正是函数调用表达式中所用的实参列表)
- b) 全部是某个类
T
的成员函数,该情况下,如果 this 在作用域中且它是指向T
或从T
派生的类的指针,那么以*this
作为隐含对象实参。否则(如果this
不在作用域中或不指向T
),以一个T
类型的虚假对象作为隐含对象实参,而如果重载决议继而选择了非静态成员函数,那么程序非良构。
调用类对象
如果 E
在函数调用表达式 E(args)
中拥有类型 cv T
,那么
- 在表达式
(E).operator()
的语境中,对名字进行 operator() 的通常查找获得 T 的函数调用运算符,并把每个找到的函数声明添加到候选函数集。 - 对于
T
或T
的基类中每个(未被隐藏的)非 explicit 的用户定义转换函数,且它的 cv 限定符与T
的 cv 限定符相同或更多,并且该转换函数转换到:
- 函数指针
- 函数指针的引用
- 函数的引用
- 那么将一个拥有独有名称的代表调用函数添加到候选函数集,该函数的首个形参作为转换结果,剩余各形参作为转换结果所接受的形参列表,而它的返回类型作为转换结果的返回类型。如果后继的重载决议选择此代表函数,那么将调用用户定义转换函数,然后调用转换的结果。
任何情况下,就重载决议而言的实参列表,是函数调用表达式的实参列表,前面加上隐含对象实参 E
(匹配到代表函数时,用户定义转换将自动将隐含对象实参转换成代表函数的首个实参)。
int f1(int); int f2(float); struct A { using fp1 = int(*)(int); operator fp1() { return f1; } // 转换到函数指针的转换函数 using fp2 = int(*)(float); operator fp2() { return f2; } // 转换到函数指针的转换函数 } a; int i = a(1); // 通过转换函数返回的指针调用 f1
调用重载运算符
如果表达式中某个运算符的至少一个实参具有类类型或枚举类型,那么内建运算符和用户定义的运算符重载都参与重载决议,所选择的候选函数集如下:
对于实参具有类型 T1
(移除 cv 限定后)的一元运算符 @
,或左操作数具有类型 T1
而右操作数具有类型 T2
(移除 cv 限定后)的二元运算符 @
,准备下列候选函数集:
operator@
进行无限定名字查找(可能涉及 实参依赖查找)所找到的所有声明,但忽略成员函数声明而且它不会阻止到下个外围作用域中继续进行查找。如果二元运算符的两个操作数,或一元运算符的唯一操作数具有枚举类型,那么只查找有形参具有该枚举类型(或到该枚举类型引用)的函数,成为非成员候选函数。
4) 重写候选:
所有情况下,在重写表达式的语境中不考虑重写候选。对于所有其他运算符,重写候选集为空。 |
(C++20 起) |
提交给重载决议的候选函数集合是以上集合的并集。就重载决议而言的实参列表由运算符的各操作数组成,除了 operator->
的情况,它的第二个操作数并非函数调用的实参(见成员访问运算符)。
struct A { operator int(); // 用户定义转换 }; A operator+(const A&, const A&); // 非成员用户定义运算符 void m() { A a, b; a + b; // 成员候选:无 // 非成员候选:operator+(a, b) // 内建候选:int(a) + int(b) // 重载决议选择 operator+(a, b) }
如果重载决议选择了内建候选,那么从类类型的操作数进行的用户定义转换序列不允许拥有第二个标准转换序列:用户定义转换函数必须直接给出期待的操作数类型:
struct Y { operator int*(); }; // Y 可转换到 int* int *a = Y() + 100.0; // 错误:指针和 double 之间没有 operator+
对于 operator,、一元 operator& 和 operator->,如果候选函数集中没有可行函数(见后述),那么将运算符解释为内建运算符。
如果对运算符 如果对运算符( 这种情况下的重载决议有一条决胜规则:非重写候选优于重写候选,且非合成重写候选优于合成重写候选。 这种具有逆序实参的查找使得可以只写 operator<=>(std::string, const char*) 与 operator==(std::string, const char*) 就生成 std::string 和 const char* 间的所有双向比较。更多细节见默认比较。 |
(C++20 起) |
由构造函数初始化
当对类类型的对象进行直接初始化或在复制初始化之外的语境中进行默认初始化时,候选函数是正在初始化的类的所有构造函数。实参列表是初始化器的表达式列表。
当对类类型对象从某个相同或派生类类型的对象进行复制初始化,或在复制初始化语境中进行默认初始化时,候选函数是正在初始化的类的所有转换构造函数。实参列表是初始化器的表达式。
通过转换进行复制初始化
如果类类型对象的复制初始化要求调用某个用户定义转换以将 cv S
类型的初始化器表达式转换到正在初始化的对象的 cv T
类型,那么下列函数是候选函数:
-
T
的所有转换构造函数 - 从
S
及它的各基类(除非隐藏)到T
或T
的派生类或到它们的引用的非 explicit 转换函数。如果此复制初始化是 cvT
的直接初始化序列的一部分(对于接受一个到 cvT
的引用的构造函数,初始化要绑定到它的首个形参的引用),那么也会考虑 explicit 转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与构造函数的首个实参或转换函数的隐式对象实参相比较。
通过转换进行非类初始化
当非类类型 cv1 T
对象的初始化要求某个用户定义转换函数,以从类类型 cv S
的初始化器表达式转换时,下列函数是候选:
-
S
及它的基类(除非隐藏)中的,产生T
类型,或可由标准转换序列转换到T
的类型,或到这些类型的引用的非 explicit 用户定义转换函数。对于选择候选函数而言,忽略返回类型上的 cv 限定符。 - 如果这是直接初始化,那么也会考虑
S
及它的基类(除非隐藏)中的,产生T
类型,或可由限定性转换转换到T
的类型,或到这些类型的引用的 explicit 用户定义转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。
通过转换进行引用初始化
在将指代 cv1 T
的引用绑定到从初始化器表达式转换到类类型 cv2 S
的左值或右值结果的引用初始化期间,为候选集选择下列函数:
-
S
及它的基类(除非隐藏)中的到以下类型的非 explicit 用户定义转换函数:
- (当初始化左值引用或到函数的右值引用时)到 cv2
T2
的左值引用 - (当初始化右值引用或到函数的左值引用时)cv2
T2
或到 cv2T2
的右值引用
- (当初始化左值引用或到函数的右值引用时)到 cv2
- 其中 cv2 T2 与 cv1 T 引用兼容
- 对于直接初始化,如果 T2 与 T 类型相同或能以限定性转换转换到 T,那么也会考虑 explicit 用户定义转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。
列表初始化
当非聚合类类型 T
的对象进行列表初始化时,进行两阶段的重载决议。
- 在阶段 1,候选函数是
T
的所有初始化器列表构造函数,而就重载决议而言的实参列表由单个初始化器列表实参组成 - 如果阶段 1 的重载决议失败,那么进入阶段 2,其中候选函数是
T
的所有构造函数,而就重载决议而言的实参列表由初始化器列表的各个单独元素所组成。
如果初始化器列表为空而 T
拥有默认构造函数,那么跳过阶段 1。
在复制列表初始化中,如果阶段 2 选择 explicit 构造函数,那么初始化非良构(与复制初始化的总体相反,它们甚至不考虑 explicit 构造函数)。
可行函数
给定以上述方式构造的候选函数集,重载决议的下一步骤是检验各个实参与形参,并将集合缩减为可行函数(viable function)的集合
为了被包含在可行函数集中,候选函数必须满足下列条件:
M
个实参,那么刚好具有 M
个形参的候选函数可行。M
个实参且候选函数的形参多于 M
个,但是第 M+1
个形参和所有后随形参都具有默认实参,那么它可行。对于剩余的重载决议,形参列表被截断到 M。
4) 如果函数拥有关联的约束,那么必须满足它。
|
(C++20 起) |
禁止用户定义转换(转换构造函数和用户定义转换函数两者)参与可能使得能应用多于一次用户定义转换的隐式转换序列。特别是,如果转换目标是构造函数的首个形参,或用户定义转换函数的隐式对象形参,而该构造/用户定义转换是下列初始化的候选,那么不考虑用户定义转换:
- 通过用户定义转换进行复制初始化
- 通过转换函数进行非类类型的初始化
- 为直接引用绑定的通过转换函数进行的初始化
- 在类的复制初始化的第二步(直接初始化)期间通过构造函数所作的初始化
struct A { A(int); }; struct B { B(A); }; B b{{0}}; // B 的列表初始化 // 候选:B(const B&)、B(B&&)、B(A) // {0} -> B&& 不可行:要调用 B(A) // {0} -> const B& :不可行:要绑定到右值,要调用 B(A) // {0} -> A 可行。调用 A(int):不禁止到 A 的用户定义转换 |
(C++11 起) |
最佳可行函数
对于每对可行函数 F1
和 F2
,对从第 i
实参到第 i
形参的转换做排行,以确定哪一个更好(除了首个实参,静态成员函数的隐式对象实参在排行上没有影响)。
如果 F1 的所有实参的隐式转换不劣于 F2 的所有实参的隐式转换,且满足下列条件,那么确定 F1
是优于 F2
的函数:
3) 或若非如此,(仅在对函数类型的引用进行直接引用绑定所作的,通过转换函数进行初始化的语境中,)F1 的返回类型是与正在初始化的引用相同种类的引用(左值或右值),而 F2 的返回类型不是
|
(C++11 起) |
6) 或若非如此,F1 与 F2 是拥有相同形参类型列表的非模板函数,且按照约束的偏序规则,F1 比 F2 更受约束
|
(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 起) |
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 是 A(A),它是复制推导候选 A x(1, 2, 3); // 使用 #3,从非模板构造函数生成 template<class T> A(T) -> A<T>; // #6,不如 #5 特殊 A a(42); // 使用 #6 推出 A<int> 并用 #1 初始化 A b = a; // 使用 #5 推出 A<int> 并用 #2 初始化 template<class T> A(A<T>) -> A<A<T>>; // #7,和 #5 一样特殊 A b2 = a; // 使用 #7 推出 A<A<int>> 并用 #1 初始化 |
(C++17 起) |
对所有可行函数进行这些逐对比较。如果刚好有一个可行函数优于所有其他函数,那么重载决议成功并调用该函数。否则编译失败。
void Fcn(const int*, short); // 重载 #1 void Fcn(int*, int); // 重载 #2 int i; short s = 0; void f() { Fcn(&i, 1L); // 第 1 个实参:&i -> int* 优于 &i -> const int* // 第 2 个实参:1L -> short 与 1L -> int 等价 // 调用 Fcn(int*, int) Fcn(&i, 'c'); // 第 1 个实参:&i -> int* 优于 &i -> const int* // 第 2 个实参:'c' -> int 优于 'c' -> short // 调用 Fcn(int*, int) Fcn(&i, s); // 第 1 个实参:&i -> int* 优于 &i -> const int* // 第 2 个实参:s -> short 优于 s -> int // 无胜者,编译错误 }
如果最佳可行函数决议到了一个可以找到多个声明的函数,且这些声明其中的两个存在于不同的作用域并指定了使得该函数可行的默认参数,那么程序非良构。
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,默认实参不会用于使函数可行 f(); // 错误:找到两次默认实参 }
隐式转换序列的排行
重载决议所考虑的实参-形参隐式转换序列与复制初始化中(对于非引用形参)所用的隐式转换对应,但在到隐含对象形参或到赋值运算符的左侧操作数的转换时不考虑创建临时对象的转换。
每种标准转换序列的类型都被赋予三个等级之一:
标准转换序列的等级是它包含的标准转换(至多可有三次转换)中的最差等级。
直接绑定引用形参到实参表达式是恒等或派生类到基类转换:
struct Base {}; struct Derived : Base {} d; int f(Base&); // 重载 #1 int f(Derived&); // 重载 #2 int i = f(d); // d -> Derived& 拥有准确匹配等级 // d -> Base& 拥有转换等级 // 调用 f(Derived&)
因为转换序列的排行只会操作类型和值类别,所以就排行而言,位域能绑定到引用形参,但如果选择了这个函数,那么程序非良构。
S1
优于标准转换序列 S2
,条件是S1
是 S2
的真子序列,排除左值变换。恒等转换序列被认为是任何非恒等转换的子序列;S1
的等级优于 S2
的等级;S1
和 S2
都绑定到某个引用形参,而它并非某个引用限定的成员函数的隐式对象形参,且 S1
绑定右值引用到右值而 S2
绑定左值引用到右值
int i; int f1(); int g(const int&); // 重载 #1 int g(const int&&); // 重载 #2 int j = g(i); // 左值 int -> const int& 是仅有的合法转换 int k = g(f1()); // 右值 int -> const int&& 优于 右值 int -> const int&
S1
和 S2
都绑定到引用形参,且 S1
绑定左值引用到函数而 S2
绑定右值引用到函数。
int f(void(&)()); // 重载 #1 int f(void(&&)()); // 重载 #2 void g(); int i1 = f(g); // 调用 #1
S1
和 S2
都绑定到仅在顶层 cv 限定性有别的引用形参,而 S1
的类型比 S2
的 cv 限定性更少。
int f(const int &); // 重载 #1 int f(int &); // 重载 #2(都是引用) int g(const int &); // 重载 #1 int g(int); // 重载 #2 int i; int j = f(i); // 左值 i -> int& 优于 左值 int -> const int& // 调用 f(int&) int k = g(i); // 左值 i -> const int& 排行为准确匹配 // 左值 i -> 右值 int 排行为准确匹配 // 有歧义的重载:编译错误
|
(C++20 前) |
能通过限定性转换将 |
(C++20 起) |
int f(const int*); int f(int*); int i; int j = f(&i); // &i -> int* 优于 &i -> const int*,调用 f(int*)
U1
优于用户定义转换序列 U2
,如果它们调用相同的构造函数/用户定义转换函数,或以聚合初始化初始化相同的类,而任一情况下 U1
中的第二标准转换序列优于 U2
中的第二标准转换序列:
struct A { operator short(); // 用户定义转换函数 } a; int f(int); // 重载 #1 int f(float); // 重载 #2 int i = f(a); // A -> short,后随 short -> int(等级为提升) // A -> short,后随 short -> float(等级为转换) // 调用 f(int)
L1
优于列表初始化序列 L2
,如果 L1
初始化 std::initializer_list 形参而 L2
没有。
void f1(int); // #1 void f1(std::initializer_list<long>); // #2 void g1() { f1({42}); } // 选择 #2 void f2(std::pair<const char*, const char*>); // #3 void f2(std::initializer_list<std::string>); // #4 void g2() { f2({"foo","bar"}); } // 选择 #4
6) 列表初始化序列 L1 优于列表初始化序列 L2 ,如果对应形参是到数组的引用且 L1 转换到“N1 个 T 的数组”,L2 转换到“N2 个 T 的数组”,而 N1 小于 N2。
|
(C++11 起) (C++20 前) |
6) 列表初始化序列 L1 优于列表初始化序列 L2 ,如果 L1 与 L2 均转换到相同元素类型的数组,且
void f(int (&&)[] ); // 重载 #1 void f(double (&&)[] ); // 重载 #2 void f(int (&&)[2]); // 重载 #3 f({1}); // #1:由于转换优于 #2,由于边界优于 #3 f({1.0}); // #2:double -> double 优于 double -> int f({1.0, 2.0}); // #2:double -> double 优于 double -> int f({1, 2}); // #3:-> int[2] 优于 -> int[], // 而 int -> int 优于 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); // 在 float 和 std::float32_t 的转换等级相等的实现中会调用 f(std::float32_t) int j = f(y); // 错误:有歧义,没有相等的转换等级 |
(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&);}; // 转换构造函数 class B { operator A (); }; // 用户定义转换函数 class C { C (B&); }; // 转换构造函数 void f(A) {} // 重载 #1 void f(C) {} // 重载 #2 B b; f(b); // B -> A 经由构造函数或 B -> A 经由函数(有歧义转换) // b -> C 经由构造函数(用户定义转换) // 重载 #1 和 #2 的转换无法辨别;编译失败
列表初始化中的隐式转换序列
在列表初始化中,实参是 花括号初始化器列表,但它不是表达式,所以到就重载决议而言的形参类型的隐式转换序列以下列规则决定:
- 如果形参类型是某聚合体
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}; // 选择 #1(右值 double -> double:恒等转换) void g(A); g({"foo", "bar"}); // 选择 #3(左值 const char[4] -> std::string:用户定义转换)
- 否则,如果形参类型是“ N 个 T 的数组”(这只对到数组的引用发生),那么初始化器列表必须有 N 个或更少的元素,且所用的隐式转换序列是将列表(或空花括号对,如果
{}
小于 N)的每个元素转换到T
所需的最坏隐式转换序列。
|
(C++20 起) |
typedef int IA[3]; void h(const IA&); void g(int (&&)[]) h({1, 2, 3}); // int -> int 恒等转换 g({1, 2, 3}); // C++20 起同上
- 否则,如果形参类型是非聚合类类型
X
,那么重载决议选取 X 的构造函数 C 以从实参初始化器列表初始化
- 如果 C 是非 initializer_list 构造函数且而该初始化器列表拥有单个元素,它的类型是可有 cv 限定的 X,那么隐式转换序列具有准确匹配等级。如果该初始化器列表拥有单个元素,它具有可有 cv 限定的派生自 X 的类型,那么隐式转换序列具有转换等级。(注意它和聚合体的区别:聚合体在考虑聚合初始化前直接从单元素初始化器列表进行初始化,而非聚合体在考虑任何其他构造函数之前先考虑 initializer_list 构造函数)
- 否则,隐式转换序列是以恒等转换作为第二标准转换序列的用户定义转换序列。
如果有多个构造函数可行,但是没有一个优于其他所有构造函数,那么隐式转换序列是有歧义的转换序列。
struct A { A(std::initializer_list<int>); }; void f(A); struct B { B(int, double); }; void g(B); g({'a', 'b'}); // 调用 g(B(int,double)),用户定义转换 // g({1.0, 1,0}); // 错误:double->int 是窄化转换,不能在列表初始化中出现 void f(B); // f({'a', 'b'}); // f(A) 与 f(B) 都是用户定义转换
- 否则,如果形参类型是可按照聚合初始化从初始化器列表初始化的聚合体,那么隐式转换序列是以恒等转换作为第二标准转换序列的用户定义转换序列:
struct A { int m1; double m2; }; void f(A); f({'a', 'b'}); // 调用 f(A(int, double)),用户定义转换
- 否则,如果形参是引用,那么应用引用初始化规则:
struct A { int m1; double m2; }; void f(const A&); f({'a', 'b'}); // 创建临时量,调用 f(A(int, double))。用户定义转换
- 否则,如果形参类型不是类且初始化器列表拥有一个元素,那么隐式转换序列是将该元素转换到形参类型所要求者。
- 否则,如果形参类型不是类且初始化器列表没有元素,那么隐式转换序列是恒等转换。
如果实参是指派初始化器列表,那么只有在形参拥有聚合类型且该类型能按照聚合初始化的规则从初始化器列表初始化时,转换才可行。此时隐式转换序列是以恒等转换作为第二标准转换序列的用户定义转换序列。 如果在重载决议后,聚合体各成员的声明顺序与所选择的重载不匹配,那么形参的初始化非良构。 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:调用 #1 f({.y = 2, .x = 1}, 0); // 错误:选择 #1,初始化由于不匹配的成员顺序失败 g({.x = 1, .y = 2}); // 错误:在 #3 和 #4 间有歧义 } |
(C++20 起) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1 | C++98 | 选择可能有(来自不同作用域的)不同默认实参的相同函数时的行为未指明 | 此时程序非良构 |
CWG 83 | C++98 | 从字符串字面量到 char* 的转换序列优于从它 到 const char* 的转换序列,即使前者已弃用 |
降低该已弃用的转换序列的排行 (该隐式转换已在 C++11 移除) |
CWG 162 | C++98 | 在 &F(args) 的场合下,F 命名的重载集包含非静态成员函数时不合法
|
该场合下在重载决议选择了 非静态成员函数时才不合法 |
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 | 声明带有引用限定符的非 explicit 的用户定义转换函数没有对应的代表函数 | 它现在有对应的代表函数 |
CWG 1467 | C++11 | 略去了聚合体和数组的同类型列表初始化 | 定义这种初始化 |
CWG 1601 | C++11 | 从 enum 转换到它的底层类型不偏好固定的底层类型 | 底层类型比提升后的该类型更受偏好 |
CWG 1608 | C++98 | 实参类型是 T1 的一元运算符 @ 的成员候选集在 T1 是当前正在定义的类时为空
|
此时该集是 T1::operator@ 的有限定名字查找的结果 |
CWG 1687 | C++98 | 当重载决议选中内建候选时,操作数的转换没有限制 | 只转换类类型操作数,并 禁用第二段标准转换序列 |
CWG 2052 | C++98 | 非良构的合成函数模板特化也会加入候选集,导致程序非良构 | 不会加入候选集 |
CWG 2076 | C++11 | CWG 问题 1467 的解决方案导致列表初始化中双层 初始化器列表中的单个初始化器也适用用户定义转换 |
此时不适用用户定义转换 |
CWG 2137 | C++11 | 从 {X } 列表初始化 X 时,初始化器列表构造函数输给复制构造函数
|
非聚合体首先考虑初始化器列表 |
CWG 2273 | C++11 | 继承和非继承构造函数之间没有决胜规则 | 非继承构造函数胜出 |
CWG 2673 | C++20 | 拥有的形参列表与非成员重写候选的相同的内建候选会添加到内建候选列表 | 不会添加 |
P2468R2 | C++20 | a != b 即使在有匹配的 operator!= 的情况下 也会添加基于 operator== 的重写候选 |
此时不会添加重写候选 |
引用
- C++23 标准(ISO/IEC 14882:2023):
- 12.2 Overload resolution [over.match]
- C++20 标准(ISO/IEC 14882:2020):
- 12.4 Overload resolution [over.match]
- C++17 标准(ISO/IEC 14882:2017):
- 16.3 Overload resolution [over.match]
- C++14 标准(ISO/IEC 14882:2014):
- 13.3 Overload resolution [over.match]
- C++11 标准(ISO/IEC 14882:2011):
- 13.3 Overload resolution [over.match]
- C++03 标准(ISO/IEC 14882:2003):
- 13.3 Overload resolution [over.match]