命名空间
变体
操作

重载决议

来自 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 及其基类(除非隐藏)到 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 起)

[编辑] 构造函数候选的附加规则

定义为删除的默认移动构造函数移动赋值运算符不包含在候选函数集中。

如果满足以下所有条件,则在构造类型为 D 的对象时,从类类型 C 继承的构造函数(其第一个参数类型为“对 P 的引用”,包括从模板实例化的此类构造函数)将从候选函数集中排除:

  • 参数列表只有一个参数。
  • CP 引用相关
  • PD 引用相关。
(自 C++11 起)

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

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

类似地,调用成员函数的对象作为隐含对象参数添加到参数列表的开头。

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

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

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

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

(直到 C++23)

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

1) 用户定义的转换不能应用于隐式对象参数
2) 右值可以绑定到非常量隐式对象参数 (除非这是针对引用限定的成员函数)(自 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) 如果任何参数具有引用类型,则在此步骤中会考虑引用绑定:如果右值参数对应于非常量左值引用参数或左值参数对应于右值引用参数,则该函数不可行。

禁止用户定义的转换(包括转换构造函数和用户定义的转换函数)参与隐式转换序列,因为它会使得应用多个用户定义的转换成为可能。具体来说,如果转换的目标是构造函数的第一个参数或用户定义的转换函数的隐式对象参数,并且该构造函数/用户定义的转换是以下候选,则不考虑它们:

  • 通过列表初始化进行初始化,其中初始化列表只有一个元素,该元素本身就是一个初始化列表,目标是类 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 都是模板特化,并且根据模板特化的偏序规则F1F2 更特化,或者,如果不是这样,
6) F1F2 是满足以下所有条件的非模板函数
  • 它们具有相同的参数类型列表,忽略显式对象参数的类型(自 C++23 起)
  • 如果它们是成员函数,则它们都是同一类的直接成员。
  • 如果它们都是非静态成员函数,则它们的对象参数具有相同的类型。
  • 根据约束的偏序规则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 的构造函数,F2 是 D 的基类 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) 如果用户定义的转换序列 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) 如果相应的参数是对数组的引用,并且 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) 如果 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 的转换更好

不明确的转换序列被 xếp hạng 为用户定义的转换序列,因为一个参数的多个转换序列只有在它们涉及不同的用户定义的转换时才会存在

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 元素,则隐式转换序列具有精确匹配等级。如果初始化列表具有一个可能 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'});    // 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 当选择具有可能不同的默认参数(来自不同作用域)的相同函数时,行为未指定
函数
参数
在这种情况下,程序格式错误
CWG 83 C++98 从字符串字面量到 char* 的转换序列优于到 const char* 的转换序列

即使前者已被弃用
降低了已弃用转换的等级(它在 C++11 中被移除)

CWG 162 C++98 如果 F 命名的重载集中包含非静态成员函数,则在 &F(args) 的情况下无效
仅当在这种情况下重载决议选择非静态成员函数时才无效

CWG 233 C++98 在使用用户定义转换进行重载决议时,引用和指针的处理方式不一致
它们以一致的方式处理
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 使用引用限定符声明的非显式用户定义转换函数没有相应的代理函数
它具有相应的代理函数
CWG 1467 C++11 聚合体和数组的同类型列表初始化被忽略
初始化已定义
CWG 1601 C++11 从枚举到其底层类型的转换
固定类型优于它提升到的类型
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 具有与重写后的非成员候选相同的参数列表的内置候选

已添加到内置候选列表中
未添加
CWG 2712 C++98 当考虑内置赋值运算符时,第一个参数无法绑定到临时对象,这已经是不可能的[1]

删除了冗余要求
CWG 2713 C++20 即使参数是引用,也应用了关于指定初始化列表的转换限制
在这种情况下不受限制
CWG 2789 C++23 在比较参数类型列表时包含了显式对象参数
排除
CWG 2856 C++11 复制列表初始化上下文中默认初始化的重载决议仅考虑转换构造函数
考虑所有构造函数
P2468R2 C++20 即使存在匹配的 operator!=,也会为 a != b 添加基于 operator== 重写的候选
未添加
  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]

[编辑] 另请参阅