Namespaces
Variants

Overload resolution

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

为了编译函数调用,编译器必须首先执行 名称查找 ,对于函数而言,这可能涉及 实参依赖查找 ,而对于函数模板,其后可能还会进行 模板实参推导

如果一个名称指向多个实体,则称其为 重载 ,编译器必须确定要调用哪个重载。简而言之,参数与实参最匹配的重载会被调用。

具体而言,重载解析通过以下步骤进行:

  1. 构建 候选函数集
  2. 将集合精简至仅包含 可行函数
  3. 分析集合以确定唯一的 最佳可行函数 (此过程可能涉及 隐式转换序列排序 )。
void f(long);
void f(float);
f(0L); // 调用 f(long)
f(0);  // 错误:重载调用不明确

除了函数调用之外,重载函数名称可能出现在其他几种语境中,此时适用不同规则:参见 重载函数地址

如果函数无法通过重载决议被选中,则无法使用该函数(例如它是一个 模板化实体 且存在失败的 约束 )。

目录

候选函数

在重载决议开始前,通过名称查找和模板实参推导选出的函数将共同构成 候选函数集 。具体细节取决于进行重载决议的上下文环境。

命名函数调用

如果在 函数调用表达式 E ( args ) 中的 E 命名了一组重载函数和/或函数模板(但不包括可调用对象),则遵循以下规则:

  • 如果表达式 E 的形式为 PA - > B A. B (其中 A 具有类类型 cv T ),则 B 将作为 T 的成员函数进行 查找 。通过该查找找到的函数声明即为候选函数。用于重载决议的实参列表包含类型为 cv T 的隐式对象实参。
  • 如果表达式 E 主表达式 ,则按照函数调用的常规规则(可能涉及 ADL )进行名称 查找 。通过此查找找到的函数声明(根据查找机制)分为两种情况:
  • 所有非成员函数(此时用于重载决议的参数列表正是函数调用表达式中使用的参数列表)
  • 某个类 T 的所有成员函数,此时若 this 在作用域内且为指向 T 或其派生类的指针,则使用 * this 作为隐含对象参数。否则(若 this 不在作用域内或不指向 T ),将使用 T 类型的伪对象作为隐含对象参数,且若重载决议随后选择了非静态成员函数,则程序非良构。

类对象调用

如果 函数调用表达式 E ( args ) 中的 E 具有类类型 cv T ,则

  • 通过常规 查找 在表达式 ( E ) . operator ( ) 的上下文中获取 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 限定符后),将准备以下候选函数集合:

1) 成员候选集 :若 T1 是完整类或当前正在定义的类,则成员候选集为对 T1::operator@ 执行 限定名称查找 的结果。在所有其他情况下,成员候选集为空。
2) 非成员候选函数 :对于允许非成员形式的 运算符重载 ,通过表达式上下文中对 operator@ 非限定名称查找 (可能涉及 ADL )找到的所有声明,但成员函数声明会被忽略且不会阻止查找继续进入下一个外围作用域。如果二元运算符的两个操作数或一元运算符的唯一操作数具有枚举类型,则从查找集中成为非成员候选函数的仅有那些参数具有该枚举类型(或对该枚举类型的引用)的函数
3) 内建候选函数 :对于 operator, 、一元 operator & operator - > ,其内建候选函数集为空。对于其他运算符,内建候选函数是 内建运算符页面 中列出的函数,只要所有操作数都能隐式转换为其参数类型。若某个内建候选函数的参数列表与非成员候选函数 或重写的非成员候选函数 (C++20 起) (非函数模板特化)的参数列表相同,则不会将该内建候选函数加入候选列表。当考虑内建赋值运算符时,对其第一参数的转换受到限制:仅考虑 标准转换序列
4) 重写候选函数
  • 对于四个关系运算符表达式 x < y x <= y x > y x >= y ,所有找到的成员、非成员及内置 operator <=> 都会被加入候选集。
  • 对于四个关系运算符表达式 x < y x <= y x > y x >= y 以及三路比较表达式 x <=> y ,会为每个找到的成员、非成员及内置 operator <=> 添加一个参数顺序反转的合成候选函数。
  • 对于 x ! = y ,所有找到的成员、非成员及内置 operator == 都会被加入候选集,除非已存在匹配的 operator ! =
  • 对于相等运算符表达式 x == y x ! = y ,会为每个找到的成员、非成员及内置 operator == 添加一个参数顺序反转的合成候选函数,除非已存在匹配的 operator ! =
在所有情况下,重写候选函数不会在重写表达式的上下文中被考虑。对于所有其他运算符,重写候选集为空。
(since 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;          // 错误:指针与双精度浮点数之间没有 operator+

对于 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::string const char * 之间的双向比较。详见 默认比较

(C++20 起)

通过构造函数进行初始化

当类类型的对象进行 直接初始化 默认初始化 (包括在 拷贝列表初始化 上下文中的默认初始化) (C++11 起) 时,候选函数是被初始化类的所有构造函数。实参列表是初始化器的表达式列表。

否则,候选函数均为被初始化类的 转换构造函数 。实参列表即初始化器的表达式。

对于在复制列表初始化上下文中的默认初始化,如果选择了 explicit 构造函数,则该初始化是病式的。

(since 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 T2 cv1 T 引用兼容的
  • 对于直接初始化,如果 T2 T 类型相同或可通过限定转换转换为 T 类型,则显式用户定义转换函数也会被纳入考虑范围。

无论哪种情况,用于重载决议的参数列表都包含单个参数,即初始化表达式,该表达式将与转换函数的隐式对象参数进行比较。

列表初始化

当非聚合类类型 T 的对象进行 列表初始化 时,会进行两阶段重载解析。

  • 在第1阶段,候选函数是 T 的所有初始化列表构造函数,用于重载决议的实参列表由单个初始化列表实参组成
  • 若第1阶段重载决议失败,则进入第2阶段,此时候选函数是 T 的所有构造函数,用于重载决议的实参列表由初始化列表的各个独立元素组成

如果初始化列表为空且 T 具有默认构造函数,则跳过阶段 1。

在复制列表初始化中,若第二阶段选择了显式构造函数,则该初始化格式错误(这与所有其他复制初始化场景不同,在那些场景中甚至不会考虑显式构造函数)。

函数模板候选者的附加规则

如果名称查找找到一个函数模板,将执行 模板实参推导 并检查所有显式模板实参,以确定在此情况下可使用的模板实参值(如果存在):

  • 若两者均成功,则使用模板参数合成对应函数模板特化的声明,这些特化声明将被加入候选集,且此类特化会像非模板函数一样处理,除非下文决胜规则中另有说明。
  • 若参数推导失败,或合成的函数模板特化将形成病式代码,则不会有函数被加入候选集(参见 SFINAE )。

如果一个名称引用一个或多个函数模板,并且同时引用一组重载的非模板函数,那么这些函数以及从模板生成的实例化都是候选函数。

参见 函数模板重载 获取更多细节。

如果构造函数模板或转换函数模板具有 条件性 explicit 说明符 ,且该说明符恰好是 值依赖的 ,则在推导完成后,若上下文要求非 explicit 的候选函数,而生成的显式特化是 explicit 的,则该特化会从候选集中移除。

(since C++20)

构造函数候选的附加规则

被定义为弃置的默认 移动构造函数 移动赋值运算符 会从候选函数集中被排除。

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

  • 实参列表仅有一个实参
  • C P 具有 引用关联性
  • P D 具有引用关联性
(since 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); // 错误:用户定义转换不能应用于
           // 隐式对象参数
static_cast<B&>(a).f(1); // 正确

可行函数

给定如上所述构建的候选函数集合,重载解析的下一步是检查实参和形参,以将该集合缩减为 可行函数 的集合

要被纳入可行函数集合,候选函数必须满足以下条件:

1) 若存在 M 个实参,则恰好具有 M 个形参的候选函数是可行的
2) 若候选函数的参数数量少于 M ,但包含 省略号参数 ,则该函数为可行函数。
3) 若候选函数的参数数量超过 M 个,且从第 M+1 个参数开始的所有后续参数均具有默认实参,则该函数为可行函数。在后续的重载决议过程中,参数列表将在第 M 个参数处截断。
4) 若函数具有关联的 约束 ,则必须满足该约束
(since C++20)
5) 对于每个实参,必须至少存在一个隐式转换序列能将其转换为对应的形参。
6) 若任何参数具有引用类型,此步骤将考虑引用绑定:若右值实参对应非const左值引用参数,或左值实参对应右值引用参数,则该函数不可行。

用户定义转换(包括转换构造函数和用户定义转换函数)在可能应用多个用户定义转换的隐式转换序列中被禁止参与。具体而言,当转换目标是构造函数的第一个参数或用户定义转换函数的隐式对象参数,且该构造函数/用户定义转换是候选函数时,这些转换将不被考虑。

  • 通过列表初始化进行初始化,其中初始化列表恰好包含一个元素,该元素本身也是一个初始化列表,且目标是类 X 构造函数的第一个参数,转换目标为 X 或(可能带有 cv 限定符的) X 的引用:
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 更优的函数,并且

1) 至少存在一个 F1 的参数,其隐式转换比 F2 对应参数的隐式转换 更优 ,或者,若非如此,
2) (仅适用于通过转换进行的非类初始化场景)从 F1 结果到被初始化类型的标准转换序列,优于从 F2 结果出发的标准转换序列;若此项不适用,则
3) (仅在通过转换函数进行函数类型直接引用绑定的初始化语境中) F1 的结果与待初始化引用具有相同类型的引用(左值或右值),而 F2 的结果不具备该特性,或者,若非此种情况,
(since C++11)
4) F1 是一个非模板函数,而 F2 是一个模板特化,或者,如果不是这种情况,
5) F1 F2 均为模板特化,且根据 模板特化的偏序规则 F1 更为特化;若不符合此情况,则
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(); // 调用 #2
}
或者,若非上述情况,
(since 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 不是,或者,若非如此,
(since 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); // 重载 #1
void Fcn(int*, int);         // 重载 #2
int i;
short s = 0;
void f() 
{
    Fcn(&i, 1L);  // 第一个参数:&i → int* 优于 &i → const int*
                  // 第二个参数:1L → short 与 1L → int 等价
                  // 调用 Fcn(int*, int)
    Fcn(&i, 'c'); // 第一个参数:&i → int* 优于 &i → const int*
                  // 第二个参数:'c' → int 优于 'c' → short
                  // 调用 Fcn(int*, int)
    Fcn(&i, s);   // 第一个参数:&i → int* 优于 &i → const int*
                  // 第二个参数: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); // 正确:默认参数未用于可行性检查
    f();  // 错误:发现重复的默认参数
}

隐式转换序列的排序

重载决议所考虑的实参-形参隐式转换序列对应于 隐式转换 拷贝初始化 (针对非引用形参)中的运用,但需排除以下情况:当考虑转换至隐式对象形参或赋值运算符左操作数时,不会考虑那些会创建临时对象的转换。 当形参为静态成员函数的隐式对象形参时,其隐式转换序列是一个标准转换序列,该序列既不优于也不劣于任何其他标准转换序列。 (since C++23)

每个 标准转换序列类型 被赋予以下三个等级之一:

1) 精确匹配 :无需转换,左值到右值转换,限定符转换, 函数指针转换, (C++17 起) 类类型到相同类的用户定义转换
2) 提升 : 整型提升,浮点提升
3) 转换 : 整型转换,浮点转换,浮点-整型转换,指针转换,成员指针转换,布尔转换,派生类到其基类的用户定义转换

标准转换序列的等级是其包含的标准转换等级中最差者(最多可能包含 三次转换

引用参数直接绑定到实参表达式时,要么是同一性转换,要么是派生类到基类的转换:

struct Base {};
struct Derived : Base {} d;
int f(Base&);    // 重载 #1
int f(Derived&); // 重载 #2
int i = f(d); // d -> Derived& 具有精确匹配等级
              // d -> Base& 具有转换等级
              // 调用 f(Derived&)

由于转换序列的排序仅基于类型和值类别, 位域 可以为了排序目的绑定到引用参数,但如果该函数被选中,则会导致程序格式错误。

1) 标准转换序列始终比用户定义转换序列或省略号转换序列 更优
2) 用户自定义转换序列始终 优于 省略号转换 序列。
3) 当满足以下条件时,标准转换序列 S1 优于标准转换序列 S2
a) S1 S2 的真子序列,不包括左值变换;恒等转换序列被视为任何非恒等转换的子序列,或者若非如此,
b) S1 的秩优于 S2 的秩,或者,若非如此,
c) 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&
或者,如果不行的话,
d) S1 S2 均绑定到引用参数,且 S1 绑定函数左值引用,而 S2 绑定函数右值引用:
int f(void(&)());  // 重载 #1
int f(void(&&)()); // 重载 #2
void g();
int i1 = f(g); // 调用 #1
或者,如果不行的话,
e) S1 S2 仅在限定转换方面存在差异,且

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* 优于 &i -> const int*,调用 f(int*)
或者,如果不行的话,
f) 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 属于精确匹配等级
              // 重载歧义:编译错误
或者,如果不行的话,
g) S1 S2 绑定相同引用类型“指向 T 的引用”,并分别具有源类型 V1 V2 ,其中从 V1 * T * 的标准转换序列优于从 V2 * T * 的标准转换序列:
struct Z {};
struct A
{
    operator Z&();
    operator const Z&();  // 重载 #1
};
struct B
{
    operator Z();
    operator const Z&&(); // 重载 #2
};
const Z& r1 = A();        // 正确,使用 #1
const Z&& r2 = B();       // 正确,使用 #2
4) 若两个用户定义转换序列 U1 U2 调用相同的构造函数/用户定义转换函数,或通过聚合初始化初始化同一类,且在任何一种情况下 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}); }                       // 选择 #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 转换为“N1个T的数组”类型, L2 转换为“N2个T的数组”类型,且 N1 小于 N2,则 L1 优于 L2。
(since C++11)
(until C++20)
6) 当对应参数是数组引用时,若列表初始化序列 L1 L2 转换为相同元素类型的数组,且满足以下任一条件,则 L1 优于 L2:
  • L1 初始化的元素数量 N1 小于 L2 初始化的元素数量 N2,或
  • N1 等于 N2 且 L2 转换为未知边界数组而 L1 不转换。
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
(since C++20)

如果两个转换序列因具有相同等级而无法区分,则适用以下附加规则:

1) 不涉及指向 bool 的指针或指向成员的指针到 bool 的转换优于涉及此类指针的转换。
2) 将底层类型固定的 枚举 转换为其底层类型的转换,优于转换为提升后底层类型的转换(若两种类型不同)。
enum num : char { one = '0' };
std::cout << num::one; // '0', not 48
(since 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); // 错误:存在歧义,没有相等的转换等级
(since C++23)
4) 将指向派生类的指针转换为指向基类的指针,优于将指向派生类的指针转换为指向 void 的指针;而将指向基类的指针转换为 void 的指针,优于将指向派生类的指针转换为 void 的指针。
5) Mid (直接或间接)派生自 Base ,且 Derived (直接或间接)派生自 Mid
a) Derived * Mid * 的转换优于 Derived * Base * 的转换
b) Derived Mid & Mid && 优于 Derived Base & Base &&
c) Base :: * Mid :: * 的转换优于 Base :: * Derived :: * 的转换
d) Derived Mid 的转换优于 Derived Base 的转换
e) Mid * Base * 优于 Derived * Base *
f) Mid Base & Base && 优于 Derived Base & Base &&
g) Mid :: * Derived :: * 的转换优于 Base :: * Derived :: * 的转换
h) 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 的转换方式无法区分;编译失败

列表初始化中的隐式转换序列

列表初始化 中,实参是一个 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};     // 选择 #1(右值 double -> double:恒等转换)
void g(A);
g({"foo", "bar"}); // 选择 #3(左值 const char[4] -> std::string:用户定义转换)
  • 否则,如果参数类型为“N个T的数组”(仅出现在数组引用的情况下),初始化列表必须包含N个或更少的元素,并且将列表中每个元素(若列表长度小于N则为空花括号 {} )转换为 T 所需的最差隐式转换将被采用。
  • 否则,若形参类型为“未知边界T的数组”(仅发生于数组引用的情况),则使用将列表每个元素转换为 T 所需的最差隐式转换。
(since 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不是初始化列表构造函数,且初始化列表仅包含一个可能带有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,由于成员顺序不匹配导致 a 初始化失败
                            // due to non-matching member order
    g({.x = 1, .y = 2});    // 错误:在 #3 和 #4 之间存在歧义
}


(since C++20)

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 适用标准 发布时行为 正确行为
CWG 1 C++98 当选择具有可能不同默认参数(来自不同作用域)的相同函数时,行为未指定 此情况下程序非良构
CWG 83 C++98 从字符串字面量到 char * 的转换序列优于到 const char * 的转换序列
即使前者已被弃用
降低已弃用转换的等级(在C++11中已移除)
CWG 162 C++98 &F(args) 情况下,如果 F 命名的重载集包含非静态成员函数则无效 仅当重载决议在此情况下选择了非静态成员函数时才无效
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::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 在拷贝列表初始化上下文中的默认初始化重载决议仅考虑转换构造函数 考虑所有构造函数
CWG 2919 C++98 通过转换进行引用初始化的候选集依赖于初始化的目标类型 依赖于转换的目标类型
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]

另请参阅