命名空间
变体
操作

重载决议

来自 cppreference.cn
< 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]

[编辑] 另见