重载决议
为了编译函数调用,编译器必须首先进行名称查找,对于函数可能涉及参数依赖查找,而对于函数模板可能后随模板实参推导。若这些步骤产生多于一个候选函数,则进行重载决议,选择将要实际调用的函数。
通常来说,调用的函数是形参最紧密地匹配实参的候选函数。
重载函数名能出现的其他语境,见取重载函数的地址。
目录 |
[编辑] 细节
重载决议开始前,由名称查找选择和模板实参推导选择函数,组成候选函数的集合(准确的判别标准取决于重载决议发生于何种语境,见后述)。
若任何候选函数是成员函数(静态或非静态),但非构造函数,则将它当做如同它有一个额外形参(隐式对象形参),代表调用函数所用的,并出现在首个参数之前的对象。
类似地,正在调用的成员函数所用的对象,作为隐式对象实参,前附于实参列表。
对于类 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
[编辑] 候选函数
对于每个使用重载决议的语境,以独有的方式准备候选函数集和实参列表:
[编辑] 调用具名函数
若 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) 重写候选:对于六个关系运算符表达式 x==y 、 x!=y 、 x<y 、 x<=y 、 x>y 和 x>=y ,若 x<=>y @ 0 (表示 <=> 返回 std::*_ordering 而 @ 是 ==, != 、 < 、 > 、 <= 、 >= 之一,或 <=> 返回 std::*_equality 而 @ 是 == 、 != 之一)为良式则添加所有找到的成员、非成员和内建 operator<=> 到集合。另外,对于六个关系运算符表达式 x==y 、 x!=y 、 x<y 、 x<=y, x>y 和 x>=y 还有三路比较表达式 x<=>y ,若 0 @ y <=> x 为良式则对每个找到的成员、非成员和内建 operator<=> 添加参数逆序的合成候选。该情况下,在重写的表达式ID语境中不考虑重写候选。对于所有其他运算符,重写候选集为空。
|
(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-> ,若候选函数集中无可达函数(见后述),则将运算符转译为内建。
|
若重载决议对运算符 此情况下的重载决议有最终决断:偏好非重写候选甚于重写候选,且偏好非合成重写候选优于合成重写候选。 这种带参数逆序的查找使得可以只写一个 operatror<=>(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的所有 initializer_list 构造函数,而为重载决议目的的实参列表由单个 initializer list 实参组成 - 若重载决议在阶段 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 // calls 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 限定的子集
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++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 的转换不可辨别;编译失败
[编辑] 列表初始化中的隐式转换序列
在列表初始化中,实参是 braced-init-list ,而它不是表达式,故到为重载决议目的的形参类型的隐式转换序列以下列规则决定:
|
(C++14 起) |
- 若形参类型为 std::initializer_list<X> ,而有从每个初始化器列表元素到
X的非窄化隐式转换,则为重载决议目的的隐式转换序列是所需的最坏转换。若 braced-init-list 为空,则转换序列为恒等转换。
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所需的最坏隐式转换序列。
typedef int IA[3]; void h(const IA&); h({1,2,3}); // int -> int 恒等转换
- 否则,若形参类型为非聚合类类型
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 时, initializer_list 构造函数输给复制构造函数 | 非聚合体首先考虑 initializer_list |
[编辑] 参阅
[编辑] 引用
- 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]

