不幸的是,这个问题的答案可能比你要找的更复杂。正如Lightness Races in Orbit指出的那样,编译器确实会拒绝模棱两可的转换,但是转换是否模棱两可?让我们来看看几个案例。所有参考均指向 C++11 标准。
显式转换
这并没有直接解决您的问题,因为您询问了隐式转换,但是由于 Orbit 中的 Lightness Races 提供了显式转换的示例,所以无论如何我都会讨论它。
A
从到B
何时执行显式转换:
- 您使用语法
(B)a
,其中a
是 type A
,在这种情况下相当于static_cast<B>(a)
(C++11 标准,§5.4/4)。
- 您使用静态强制转换,在这种情况下,它将创建一个临时对象,该临时对象的初始化方式与声明
B t(a);
初始化的方式相同t
;(§5.2.9/4)
- 您使用语法
B(a)
,它等同于并因此也与声明中的初始化(§5.2.3/1)(B)a
做同样的事情B t(a);
B
因此,在每种情况下,使用类型的值作为参数对类型的纯右值执行直接初始化A
。§8.5/16 规定只考虑构造函数,因此B::B(const A&)
将被调用。(有关更多详细信息,请在此处查看我的答案:https ://stackoverflow.com/a/22444974/481267 )
复制初始化
在复制初始化中
B b = a;
a
type的值A
首先使用用户定义的转换序列转换为临时类型B
,这是一个隐式转换序列。然后这个临时用于直接初始化b
。
因为这是不同类类型的对象对类类型的复制初始化,所以转换构造函数B::B(const A&)
和转换函数A::operator B()
都是转换的候选对象(第 13.3.1.4 节)。调用后者是因为它赢得了重载决议。请注意,如果B::B
有参数A&
而不是const A&
,则重载将是模棱两可的,程序将无法编译。有关标准的详细信息和参考,请参阅此答案:https ://stackoverflow.com/a/1384044/481267
复制列表初始化
复制列表初始化
B b = {a};
仅考虑B
(§8.5.4/3) 的构造函数,而不考虑 的转换函数A
,因此B::B(const A&)
将被调用,就像在显式转换中一样。
函数参数的隐式转换
如果我们有
void f(B b);
A a;
f(a);
然后编译器必须选择最佳隐式转换序列来转换a
为类型B
,以便将其传递给f
. 为此,考虑用户定义的转换序列,包括标准转换、用户定义的转换和另一个标准转换(第 13.3.3.1.2/1 节)。用户定义的转换可以通过转换构造函数B::B(const A&)
或转换函数A::operator B()
进行。
这就是棘手的地方。标准中有一些令人困惑的措辞:
由于隐式转换序列是一种初始化,因此在为用户定义的转换序列选择最佳的用户定义转换时,适用于通过用户定义的转换进行初始化的特殊规则(参见 13.3.3 和 13.3.3.1)。
(§13.3.3.1.2/2)
长话短说,这意味着用户定义的转换序列 from A
to中的用户定义的转换B
本身会受到重载决议的影响;A::operator B()
获胜B::B(const A&)
是因为前者的 cv 限定较少(如在复制初始化情况下),如果我们有B::B(A&)
而不是B::B(const A&)
. 请注意,这不会导致重载决议的无限递归,因为不允许用户定义的转换将参数转换为用户定义的转换的参数类型。
退货声明
在
B foo() {
return A();
}
表达式A()
被隐式转换为类型B
(第 6.6.3/2 节),因此适用与函数参数的隐式转换相同的规则;A::operator B()
将被调用,如果我们有B::B(A&)
. 但是,如果改为
return {A()};
那么这将是一个复制列表初始化(再次第 6.6.3/2 节);B::B(const A&)
将被调用。
注意:处理异常时不会尝试用户定义的转换;一个catch(B)
块不会处理一个throw A();
.