命名空间
变体
操作

重载决议

来自 cppreference.cn
< 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)
存储期说明符
初始化
 
 

为了编译函数调用,编译器必须首先执行名字查找,对于函数,这可能涉及到实参依赖查找,而对于函数模板,之后可能是模板实参推导

如果名字指代多于一个实体,则称之为重载,并且编译器必须确定要调用哪个重载。简单来说,参数与实参最匹配的重载版本会被调用。

详细来说,重载决议通过以下步骤进行

  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 及其基类(除非被隐藏)到 T 或从 T 派生的类或对这种类型的引用的非explicit转换函数。如果此复制初始化是 cv T 的直接初始化序列的一部分(初始化要绑定到构造函数的第一个形参的引用,该构造函数采用对 cv T 的引用),则也会考虑显式转换函数。

无论哪种方式,为了重载决议的目的,实参列表都由一个实参组成,该实参是初始化器表达式,它将与构造函数的第一个实参或转换函数的隐式对象实参进行比较。

[编辑] 通过转换进行非类初始化

当非类类型 cv1 T 的对象的初始化需要用户定义的转换函数以从类类型 cv S 的初始化器表达式进行转换时,以下函数是候选函数

  • 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 的引用”(reference to P),则在构造类型为 D 的对象时,会从候选函数集中排除,如果满足以下所有条件:

  • 实参列表正好有一个实参。
  • CP 引用相关 (reference-related)。
  • PD 引用相关。
(自 C++11 起)

[编辑] 成员函数候选的附加规则

如果任何候选函数是成员函数(静态或非静态),且没有显式对象参数(自 C++23 起),但不是构造函数,则将其视为具有一个额外的参数(隐式对象参数),该参数表示调用它们的对象,并出现在第一个实际参数之前。

类似地,调用成员函数的对象会作为隐式对象实参添加到实参列表的前面。

对于类 X 的成员函数,隐式对象参数的类型受成员函数的 cv 限定符和 ref 限定符的影响,如成员函数中所述。

为了确定隐式对象参数的类型,用户定义的转换函数被认为是隐式对象实参的成员。

通过 using 声明引入派生类的成员函数被认为是派生类的成员,用于定义隐式对象参数的类型

对于静态成员函数,隐式对象参数被认为与任何对象匹配:其类型不被检查,并且不为其尝试转换序列。

(直到 C++23)

对于重载解析的其余部分,隐式对象实参与其他实参没有区别,但以下特殊规则适用于隐式对象参数

1) 用户定义的转换不能应用于隐式对象参数
2) 右值可以绑定到非 const 隐式对象参数(除非这是用于 ref 限定的成员函数)(自 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 的所有实参的隐式转换,则确定 F1F2 更好,并且

1) F1 至少有一个实参的隐式转换优于 F2 的该实参的相应隐式转换;或者,如果不是这样,
2) (仅在通过转换进行非类初始化的上下文中),从 F1 的结果到要初始化的类型的标准转换序列优于F2 的结果到要初始化的类型的标准转换序列;或者,如果不是这样,
3) (仅在通过转换函数进行函数类型引用的直接引用绑定的上下文中),F1 的结果与要初始化的引用类型(左值或右值)相同,而 F2 的结果不是;或者,如果不是这样,
(自 C++11 起)
4) F1 是非模板函数,而 F2 是模板特化;或者,如果不是这样,
5) F1F2 都是模板特化,并且根据模板特化的偏序规则F1F2 更专门化;或者,如果不是这样,
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 都绑定到引用形参,而不是 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&
或者,如果不是这样,
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 的类型比 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
或者,如果不是这样,
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) 用户定义的转换序列 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 优于 列表初始化序列 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 转换为相同元素类型的数组,并且满足以下任一条件:
  • 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) 如果 MidBase 派生(直接或间接),并且 DerivedMid 派生(直接或间接)
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

[编辑] 列表初始化中的隐式转换序列

列表初始化中,实参是 花括号初始化列表,它不是表达式,因此用于重载解析的到形参类型的隐式转换序列由以下特殊规则决定:

  • 如果形参类型是某个聚合 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,则隐式转换序列具有精确匹配等级。如果初始化列表具有单个元素,该元素可能是从 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 if
T1 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
未添加
  1. 内置赋值运算符的第一个形参的类型是 “可能带有 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]

[编辑] 参见