C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
重载决议
为了编译函数调用,编译器必须首先进行名字查找,对于函数可能涉及参数依赖查找,而对于函数模板可能后随模板实参推导。若这些步骤产生多于一个候选函数,则进行重载决议,选择将要实际调用的函数。
通常来说,所调用的函数是各形参与各实参之间的匹配最紧密的候选函数。
关于其他可以出现重载函数名的语境,见重载函数的地址。
若函数无法被重载决议选择(例如它是有未被满足的制约的模板化实体),则不能指名或再使用它。
细节
重载决议开始前,将名字查找和模板实参推导所选择的函数组成候选函数的集合(确切的判别标准取决于发生重载决议的语境,见下文)。
若任何候选函数是成员函数(静态或非静态),但非构造函数,则将它当做如同它有一个额外形参(隐式对象形参),代表调用函数所用的对象,并出现在首个实际形参之前。
类似地,调用成员函数所用的对象,作为隐含对象实参,前附于实参列表。
对于类 X
的成员函数,隐含对象形参的类型受成员函数的 cv 限定和引用限定影响,如成员函数中所述。
就确定隐式对象形参类型而言,用户定义转换函数被认为是隐含对象实参的成员。
就确定隐式对象形参类型而言,由 using 声明引入到派生类中的成员函数,被认为是派生类的成员。
对于静态成员函数,其隐式对象形参被认为匹配任何对象:不检验其类型,不为之尝试转换序列。
对于重载决议的剩余部分,隐含对象实参与其他实参不可辨别,但下列特殊规则适用于隐式对象形参:
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->B
或A.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 的函数调用运算符,并把每个找到的函数声明添加到候选函数集。 - 对于
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 限定后)的二元运算符 @
,准备下列候选函数集:
operator@
进行无限定名字查找(可能涉及 ADL)所找到的所有声明,但忽略成员函数声明而且其不会阻止到下个外围作用域中继续进行查找。若二元运算符的两个操作数,或一元运算符的唯一操作数具有枚举类型,则查找集中仅有形参具有该枚举类型(或到该枚举类型引用)的函数,成为非成员候选函数。
4) 重写候选:
所有情况下,在重写表达式的语境中不考虑重写候选。对于所有其他运算符,重写候选集为空。 |
(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<=>(std::string, const char*) 与 operator==(std::string, const char*) 就生成 std::string 和 const char* 间的所有双向比较。更多细节见默认比较。 |
(C++20 起) |
由构造函数初始化
当对类类型的对象进行直接初始化或在复制初始化之外的语境中进行默认初始化时,候选函数是正在初始化的类的所有构造函数。实参列表为初始化器的表达式列表。
当对类类型对象从某个相同或派生类类型的对象进行复制初始化,或在复制初始化语境中进行默认初始化时,候选函数是正在初始化的类的所有转换构造函数。实参列表是初始化器的表达式。
通过转换进行复制初始化
若类类型对象的复制初始化要求调用某个用户定义转换函数以将 cv S
类型的初始化器表达式转换为正在初始化的对象的 cv T
类型,则下列函数是候选函数:
-
T
的所有转换构造函数 - 从
S
及其各基类(除非隐藏)到T
或T
的派生类或到它们的引用的非 explicit 转换函数。若此复制初始化是 cvT
的直接初始化序列的一部分(对于接受一个到 cvT
的引用的构造函数,初始化要绑定到其首个形参的引用),则亦考虑 explicit 转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与构造函数的首个实参或转换函数的隐式对象实参相比较。
通过转换进行非类初始化
当非类类型 cv1 T
对象的初始化要求某个用户定义转换函数,以从类类型 cv S
的初始化器表达式转换时,下列函数为候选:
-
S
及其基类(除非隐藏)中的,产生T
类型,或可由标准转换序列转换到T
的类型,或到这些类型的引用的,非 explicit 用户定义转换函数。对于选择候选函数而言,忽略返回类型上的 cv 限定符。 - 若这是直接初始化,则亦考虑
S
及其基类(除非隐藏)中的,产生T
类型,或可由限定性转换转换到T
的类型,或到这些类型的引用的,explicit 用户定义转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。
通过转换进行引用初始化
在将指代 cv1 T
的引用绑定到从初始化器表达式转换到类类型 cv2 S
的左值或右值结果的引用初始化期间,为候选集选择下列函数:
-
S
及其基类(除非隐藏)中的,到以下类型的非 explicit 用户定义转换函数
- (当初始化左值引用或到函数的右值引用时)到 cv2
T2
的左值引用 - (当初始化右值引用或到函数的左值引用时)cv2
T2
或到 cv2T2
的右值引用
- (当初始化左值引用或到函数的右值引用时)到 cv2
- 其中 cv2 T2 与 cv1 T 引用兼容
- 对于直接初始化,若 T2 与 T 为同一类型或能以限定性转换转换到 T,则亦考虑 explicit 用户定义转换函数。
无论哪种方式,就重载决议而言的实参列表均由单个实参组成,即初始化器表达式,它将会与转换函数的隐含对象实参相比较。
列表初始化
当非聚合类类型 T
的对象进行列表初始化时,进行两阶段的重载决议。
- 在阶段 1,候选函数是
T
的所有初始化器列表构造函数,而就重载决议而言的实参列表由单个初始化器列表实参组成 - 若阶段 1 的重载决议失败则进入阶段 2,其中候选函数是
T
的所有构造函数,而就重载决议而言的实参列表由初始化器列表的各个单独元素所组成。
若初始化器列表为空而 T
拥有默认构造函数,则跳过阶段 1。
在复制列表初始化中,若阶段 2 选择 explicit 构造函数,则初始化非良构(与复制初始化的总体相反,它们甚至不考虑 explicit 构造函数)。
可行函数
给定以上述方式构造的候选函数集,重载决议的下一步骤是检验各个实参与形参,并将集合缩减为可行函数(viable function)的集合
为了被包含在可行函数集中,候选函数必须满足下列条件:
M
个实参,则刚好具有 M
个形参的候选函数可行。M
个,而第 M+1
个形参和所有后随形参都具有默认实参,则它可行。对于剩余的重载决议,形参列表被截断到 M。
4) 若函数拥有关联的制约,则其必须得以满足。
|
(C++20 起) |
禁止用户定义转换(转换构造函数和用户定义转换函数两者)参与可能使得能应用多于一次用户定义转换的隐式转换序列。特别是,若转换目标是构造函数的首个形参,或用户定义转换函数的隐式对象形参,而该构造/用户定义转换是下列初始化的候选,则不考虑用户定义转换
- 通过用户定义转换进行复制初始化,
- 通过转换函数进行非类类型的初始化,
- 为直接引用绑定的通过转换函数进行的初始化,
- 在类的复制初始化的第二步骤(直接初始化)期间通过构造函数所作的初始化,
- 由列表初始化所作的初始化,其中的初始化器列表刚好拥有一个元素,且其自身是一个初始化器列表,而目标是类 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 的用户定义转换
最佳可行函数
对于每对可行函数 F1
和 F2
,对从第 i
实参到第 i
形参的转换做排行,以确定何者更好(除了首个实参,静态成员函数的隐式对象实参在排行上没有影响)。
若 F1 的所有实参的隐式转换不劣于 F2 的所有实参的隐式转换,且满足下列条件,则确定 F1
是优于 F2
的函数
3) 或若非如此,(仅在对函数类型的引用进行直接引用绑定所作的,通过转换函数进行初始化的语境中,)F1 的返回类型是与正在初始化的引用相同种类的引用(左值或右值),而 F2 的返回类型不是
|
(C++11 起) |
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 // 无胜者,编译错误 }
隐式转换序列的排行
重载决议所考虑的实参-形参隐式转换序列,对应于复制初始化中(对于非引用形参)所用的隐式转换,但在考虑到隐含对象形参的转换,或到赋值运算符的左侧操作数的转换时,不考虑创建临时对象的转换。
每种标准转换序列的类型都被赋予三个等级之一:
标准转换序列的等级是其所含的标准转换(至多可有三次转换)中的最差等级。
直接绑定引用形参到实参表达式是恒等或派生类到基类转换:
struct Base {}; struct Derived : Base {} d; int f(Base&); // 重载 #1 int f(Derived&); // 重载 #2 int i = f(d); // d -> Derived& 拥有准确匹配等级 // d -> Base& 拥有转换等级 // 调用 f(Derived&)
因为转换序列的排行仅操作类型和值类别,故就排行而言,位域能绑定到引用形参,但若选择了这个函数,则程序非良构。
S1
优于标准转换序列 S2
,条件为S1
为 S2
的子序列,排除左值变换。恒等转换序列被认为是任何其他转换的子序列S1
的等级优于 S2
的等级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&
S1
和 S2
都绑定到引用形参,且 S1
绑定左值引用到函数而 S2
绑定右值引用到函数。
int f(void(&)()); // 重载 #1 int f(void(&&)()); // 重载 #2 void g(); int i1 = f(g); // 调用 #1
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 排行为准确匹配 // 有歧义的重载:编译错误
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*)
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)
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 均转换到相同元素类型的数组,且
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 起) |
若两个转换序列因为拥有相同等级而不可辨别,则应用下列额外规则:
(C++11 起) |
Mid
(直接或间接)派生自 Base
,而 Derived
(直接或间接)派生自 Mid
,则对有歧义的转换序列的分级与用户定义转换序列相同,因为一个实参的多个转换序列仅若它们涉及不同的用户定义转换时才能存在:
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 的转换不可辨别;编译失败
列表初始化中的隐式转换序列
在列表初始化中,实参是 花括号初始化器列表,但它不是表达式,故到就重载决议而言的形参类型的隐式转换序列以下列规则决定:
|
(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
所需的最坏隐式转换序列。
|
(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++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++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]