CppDS.com

C++ 98 11 14 17 20 手册

重载决议

来自cppreference.com
< cpp‎ | language

为了编译函数调用,编译器必须首先进行名字查找,对于函数可能涉及参数依赖查找,而对于函数模板可能后随模板实参推导。若这些步骤产生多于一个候选函数,则进行重载决议,选择将要实际调用的函数。

通常来说,所调用的函数是各形参与各实参之间的匹配最紧密的候选函数。

关于其他可以出现重载函数名的语境,见重载函数的地址

若函数无法被重载决议选择(例如它是有未被满足的制约模板化实体),则不能指名或再使用它。

细节

重载决议开始前,将名字查找和模板实参推导所选择的函数组成候选函数的集合(确切的判别标准取决于发生重载决议的语境,见下文)。

若任何候选函数是成员函数(静态或非静态),但非构造函数,则将它当做如同它有一个额外形参(隐式对象形参),代表调用函数所用的对象,并出现在首个实际形参之前。

类似地,调用成员函数所用的对象,作为隐含对象实参,前附于实参列表。

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

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

就确定隐式对象形参类型而言,由 using 声明引入到派生类中的成员函数,被认为是派生类的成员。

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

对于重载决议的剩余部分,隐含对象实参与其他实参不可辨别,但下列特殊规则适用于隐式对象形参

1) 不能对隐式对象形参运用用户定义转换
2) 右值能绑定到非 const 的隐式对象形参(除非是对引用限定的成员函数) (C++11 起),且不影响隐式转换的等级。
struct B { void f(int); };
struct A { operator B&(); };
A a;
a.B::f(1); // 错误:不能对隐式对象形参运用用户定义转换
static_cast<B&>(a).f(1); // OK

若有任何候选函数是函数模板,则使用模板实参推导生成其特化,并把这种特化当做非模板函数对待,但在决断规则中另行有所规定。若一个名字指代一或多个函数模板,并且亦指代重载的非模板函数,则这些函数和从模板生成的特化都是候选。

若构造函数模板或转换函数模板拥有条件性 explicit 说明符,而在推导后它恰好为值待决的,若语境要求非 explicit 的候选而所生成的候选为 explicit,则从候选集中移除它。

(C++20 起)

候选函数列表中始终不包含被定义为弃置的预置移动构造函数移动赋值运算符

在构造派生类对象时,候选函数列表中不包含继承的复制和移动构造函数。

候选函数

使用重载决议的每种语境,都以其独有的方式准备其候选函数集合和实参列表:

调用具名函数

E函数调用表达式 E(args) 中指名重载的函数和/或函数模板(但非可调用对象)的集合,则遵循下列规则:

  • 若表达式 E 具有 PA->BA.B 的形式(其中 A 具有类类型 cv T),则将 B 作为 T 的成员函数查找。该查找所找到的函数声明均为候选函数。就重载决议而言,实参列表拥有 cv T 类型的隐含对象实参。
  • 若表达式 E初等表达式,则遵循函数调用的正常规则查找其名字(可能涉及 ADL)。此查找所找到的函数声明(取决于查找的工作方式)为下列之一:
a) 全部是非成员函数(该情况下,就重载决议而言,实参列表正是函数调用表达式中所用的实参列表)
b) 全部是某个类 T 的成员函数,该情况下,若 this 在作用域中且为指向 T 或从 T 派生的类的指针,则以 *this 为隐含对象实参。否则(若 this 不在作用域中或不指向 T),以一个 T 类型的虚假对象为隐含对象实参,而若重载决议继而选择了非静态成员函数,则程序非良构。

调用类对象

E函数调用表达式 E(args) 中拥有类型 cv T,则

  • 在表达式 (E).operator() 的语境中,对名字进行 operator() 的通常查找获得 T 的函数调用运算符,并把每个找到的函数声明添加到候选函数集。
  • 对于 TT 的基类中每个(未被隐藏的)非 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->,内建候选集为空。对于其他运算符,内建候选是内建运算符页面中列出的函数,只要所有操作数都能隐式转换为其各个形参。若有任何内建候选拥有的形参列表与某个并非函数模板特化的非成员候选相同,则不将该内建候选添加到内建候选列表。当考虑内建的赋值运算符时,限制从其左侧实参进行的转换:不考虑用户定义转换。
4) 重写候选
  • 对于四个关系运算符表达式 x<yx<=yx>yx>=y ,添加所有找到的成员、非成员及内建 operator<=> 到集合。
  • 对于四个关系运算符表达式 x<yx<=yx>yx>=y 还有三路比较运算符表达式 x<=>y ,对每个找到的成员、非成员及内建 operator<=> 添加二个形参顺序相反的合成候选。
  • 对于 x!=y ,添加所有找到的成员、非成员及内建 operator== 到集合。
  • 对于相等运算符表达式 x==yx!=y ,对每个找到的成员、非成员及内建 operator== 添加二个形参顺序相反的合成候选。

所有情况下,在重写表达式的语境中不考虑重写候选。对于所有其他运算符,重写候选集为空。

(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<=> 候选,则用重写的 operator<=> 候选将 x @ y 解释为重写的表达式:当所选择的候选是具有逆序形参的合成候选时,解释为 0 @ (y <=> x),否则为 (x <=> y) @ 0

若对运算符(为 ==!= )的重载决议选择了重写 operator== 候选,则其返回类型必须是(可有 cv 限定的) bool ,并使用选择的重写 operator== 候选解释 x @ y 为重写表达式:若选择的候选为拥有逆序形参的合成候选则为 y == x!(y == x) ,否则为 !(x == y)

这种情况下的重载决议有一条决胜规则:偏好非重写候选甚于重写候选,且偏好非合成重写候选甚于合成重写候选。

这种具有逆序实参的查找使得可以只写 operator<=>(std::string, const char*)operator==(std::string, const char*) 就生成 std::stringconst char* 间的所有双向比较。更多细节见默认比较

(C++20 起)

由构造函数初始化

当对类类型的对象进行直接初始化或在复制初始化之外的语境中进行默认初始化时,候选函数是正在初始化的类的所有构造函数。实参列表为初始化器的表达式列表。

当对类类型对象从某个相同或派生类类型的对象进行复制初始化,或在复制初始化语境中进行默认初始化时,候选函数是正在初始化的类的所有转换构造函数。实参列表是初始化器的表达式。

通过转换进行复制初始化

若类类型对象的复制初始化要求调用某个用户定义转换函数以将 cv S 类型的初始化器表达式转换为正在初始化的对象的 cv T 类型,则下列函数是候选函数:

  • T 的所有转换构造函数
  • S 及其各基类(除非隐藏)到 TT 的派生类或到它们的引用的非 explicit 转换函数。若此复制初始化是 cv T 的直接初始化序列的一部分(对于接受一个到 cv T 的引用的构造函数,初始化要绑定到其首个形参的引用),则亦考虑 explicit 转换函数。

无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与构造函数的首个实参或转换函数的隐式对象实参相比较。

通过转换进行非类初始化

当非类类型 cv1 T 对象的初始化要求某个用户定义转换函数,以从类类型 cv S 的初始化器表达式转换时,下列函数为候选:

  • S 及其基类(除非隐藏)中的,产生 T 类型,或可由标准转换序列转换到 T 的类型,或到这些类型的引用的,非 explicit 用户定义转换函数。对于选择候选函数而言,忽略返回类型上的 cv 限定符。
  • 若这是直接初始化,则亦考虑 S 及其基类(除非隐藏)中的,产生 T 类型,或可由限定性转换转换到 T 的类型,或到这些类型的引用的,explicit 用户定义转换函数。

无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。

通过转换进行引用初始化

在将指代 cv1 T 的引用绑定到从初始化器表达式转换到类类型 cv2 S 的左值或右值结果的引用初始化期间,为候选集选择下列函数:

  • S 及其基类(除非隐藏)中的,到以下类型的非 explicit 用户定义转换函数
  • (当初始化左值引用或到函数的右值引用时)到 cv2 T2 的左值引用
  • (当初始化右值引用或到函数的左值引用时)cv2 T2 或到 cv2 T2 的右值引用
其中 cv2 T2 与 cv1 T 引用兼容
  • 对于直接初始化,若 T2 与 T 为同一类型或能以限定性转换转换到 T,则亦考虑 explicit 用户定义转换函数。

无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。

列表初始化

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

  • 在阶段 1,候选函数是 T 的所有初始化器列表构造函数,而就重载决议而言的实参列表由单个初始化器列表实参组成
  • 若阶段 1 的重载决议失败则进入阶段 2,其中候选函数是 T 的所有构造函数,而就重载决议而言的实参列表由初始化器列表的各个单独元素所组成。

若初始化器列表为空而 T 拥有默认构造函数,则跳过阶段 1。

在复制列表初始化中,若阶段 2 选择 explicit 构造函数,则初始化非良构(与复制初始化的总体相反,它们甚至不考虑 explicit 构造函数)。

可行函数

给定以上述方式构造的候选函数集,重载决议的下一步骤是检验各个实参与形参,并将集合缩减为可行函数(viable function)的集合

为了被包含在可行函数集中,候选函数必须满足下列条件:

1) 若有 M 个实参,则刚好具有 M 个形参的候选函数可行。
2) 若候选函数的形参少于 M 个,但具有一个省略号形参,则它可行。
3) 若候选函数的形参多于 M 个,而第 M+1 个形参和所有后随形参都具有默认实参,则它可行。对于剩余的重载决议,形参列表被截断到 M。
4) 若函数拥有关联的制约,则其必须得以满足。
(C++20 起)
5) 对于每个实参,必须至少存在一个隐式转换序列,将它转换到对应的形参。
6) 若任何形参具有引用类型,则在此步骤中负责引用绑定:若右值实参对应非 const 左值引用形参,或左值实参对应右值引用形参,则函数不可行。

禁止用户定义转换(转换构造函数和用户定义转换函数两者)参与可能使得能应用多于一次用户定义转换的隐式转换序列。特别是,若转换目标是构造函数的首个形参,或用户定义转换函数的隐式对象形参,而该构造/用户定义转换是下列初始化的候选,则不考虑用户定义转换

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 的用户定义转换

最佳可行函数

对于每对可行函数 F1F2,对从第 i 实参到第 i 形参的转换做排行,以确定何者更好(除了首个实参,静态成员函数的隐式对象实参在排行上没有影响)。

若 F1 的所有实参的隐式转换不劣于 F2 的所有实参的隐式转换,且满足下列条件,则确定 F1 是优于 F2 的函数

1) 至少存在一个 F1 的实参,其隐式转换优于 F2 的该实参的对应的隐式转换
2) 或若非如此,(只在通过转换进行非类初始化的语境中,)从 F1 的返回类型到要初始化的类型的标准转换序列优于从 F2 的返回类型到该类型的标准转换序列
3) 或若非如此,(仅在对函数类型的引用进行直接引用绑定所作的,通过转换函数进行初始化的语境中,)F1 的返回类型是与正在初始化的引用相同种类的引用(左值或右值),而 F2 的返回类型不是
(C++11 起)
4) 或若非如此,F1 是非模板函数而 F2 是模板特化
5) 或若非如此,F1 与 F2 都是模板特化,且按照模板特化的偏序规则,F1 更特殊
6) 或若非如此,F1 与 F2 为拥有相同形参类型列表的非模板函数,且按照制约的偏序规则,F1 比 F2 更受制约
(C++20 起)
7) 或若非如此,F1 是类 D 的构造函数,F2 是 D 的基类 B 的构造函数,而对应每个实参的 F1 和 F2 的形参均具有相同类型
(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
                  // 无胜者,编译错误
}

隐式转换序列的排行

重载决议所考虑的实参-形参隐式转换序列,对应于复制初始化中(对于非引用形参)所用的隐式转换,但在考虑到隐含对象形参的转换,或到赋值运算符的左侧操作数的转换时,不考虑创建临时对象的转换。

每种标准转换序列的类型都被赋予三个等级之一:

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) S1S2 的子序列,排除左值变换。恒等转换序列被认为是任何其他转换的子序列
b) 或若非如此,S1 的等级优于 S2 的等级
c) 或若非如此,S1S2 都绑定到某个引用形参,而其并非某个引用限定的成员函数的隐式对象形参,且 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) 或若非如此,S1S2 都绑定到引用形参,且 S1 绑定左值引用到函数而 S2 绑定右值引用到函数。
int f(void(&)());  // 重载 #1
int f(void(&&)()); // 重载 #2
void g();
int i1 = f(g);     // 调用 #1
e) 或若非如此,则 S1S2 都绑定到仅在顶层 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 排行为准确匹配
              // 有歧义的重载:编译错误
f) 或若非如此,则 S1 与 S2 仅在限定性转换有别,而 S1 的结果的 cv 限定是 S2 的结果的 cv 限定的子集 (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*)
4) 用户定义转换序列 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)
5) 列表初始化序列 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++14 起)
(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
(C++20 起)

若两个转换序列因为拥有相同等级而不可辨别,则应用下列额外规则:

1) 涉及指针到 bool 或成员指针到 bool 的转换劣于不涉及这些的转换。
2) 如果底层类型固定的枚举的底层类型与经提升后的底层类型不同,则提升到其底层类型的转换优于提升到提升后底层类型的转换。
enum num : char { one = '0' };
std::cout << num::one; // '0',非 48
(C++11 起)
3) 派生类指针到基类指针的转换优于派生类指针到 void 指针的转换,而基类指针到 void 的转换优于派生类指针到 void 指针的转换。
4)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&);}; // 转换构造函数
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 限定)的元素组成,则隐式转换序列是将该元素转换到形参类型所要求的序列。
  • 否则,若形参类型是到字符数组的引用,而初始化器列表拥有单个元素,元素为类型适当的字符串字面量,则隐式转换序列为恒等转换。
(C++14 起)
  • 若形参类型为 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 或更少的元素,而 (C++14 起)所用的隐式转换序列是将列表(或空花括号对,若 {} 小于 N) (C++14 起)的每个元素转换到 T 所需的最坏隐式转换序列。
  • 否则,若形参类型为“ T 的未知边界数组”(这只对到数组的引用发生),则所用的隐式转换序列是将列表的每个元素转换到 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 是非初始化器列表构造函数,而该初始化器列表拥有单个元素,其类型为可为 cv 限定的 X,则隐式转换序列具有准确匹配等级。若该初始化器列表拥有单个元素,其具有可有 cv 限定的派生自 X 的类型,则隐式转换序列具有转换等级。(注意其与聚合体的区别:聚合体在考虑聚合初始化前直接从单元素初始化器列表进行初始化,而非聚合体在考虑任何其他构造函数之前考虑 initializer_list 构造函数)
(C++14 起)
  • 否则, (C++14 起)隐式转换序列是以恒等转换为第二标准转换序列的用户定义转换序列。

若多个构造函数可行,但无一优于其他,则隐式转换序列是有歧义的转换序列。

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++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1601 C++11 从 enum 转换到其底层类型不偏好固定的底层类型 底层类型比提升后的该类型更受偏好
CWG 1467 C++14 略去了聚合体和数组的同类型列表初始化 定义这种初始化
CWG 2137 C++14 从 {X} 列表初始化 X 时,初始化器列表构造函数输给复制构造函数 非聚合体首先考虑初始化器列表

参阅

引用

  • C++20 standard (ISO/IEC 14882:2020):
  • 12.4 Overload resolution [over.match]
  • C++17 standard (ISO/IEC 14882:2017):
  • 16.3 Overload resolution [over.match]
  • C++14 standard (ISO/IEC 14882:2014):
  • 13.3 Overload resolution [over.match]
  • C++11 standard (ISO/IEC 14882:2011):
  • 13.3 Overload resolution [over.match]
  • C++03 standard (ISO/IEC 14882:2003):
  • 13.3 Overload resolution [over.match]
关闭