很遗憾,这个问题的答案可能比您所寻找的要复杂。正如Lightness Races in Orbit指出的那样,编译器确实会拒绝模棱两可的转换,但是转换是否模棱两可?让我们来看看几个案例。所有引用均指向 C++11 标准。
显式转换
这并没有直接解决您的问题,因为您询问了隐式转换,但由于 Orbit 中的 Lightness Races 提供了显式转换的示例,所以我还是会复习一下。
在以下情况下执行从A 到B 的显式转换:
- 您使用语法
(B)a,其中a 的类型为A,在这种情况下将等效于static_cast<B>(a)(C++11 标准,§5.4/4)。
- 您使用静态转换,在这种情况下将创建一个临时对象,该临时对象的初始化方式与声明
B t(a); 初始化 t 的方式相同; (§5.2.9/4)
- 您使用语法
B(a),它等同于(B)a,因此也与声明B t(a);(§5.2.3/1)中的初始化相同
因此,在每种情况下,使用A 类型的值作为参数,直接初始化B 类型的纯右值。 §8.5/16 规定只考虑构造函数,因此将调用B::B(const A&)。 (更详细一点,请看我的回答:https://stackoverflow.com/a/22444974/481267)
复制初始化
在复制初始化中
B b = a;
A 类型的值 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)
长话短说,这意味着从A 到B 的用户定义转换序列中的用户定义转换本身会受到重载决议的影响; A::operator B() 胜过 B::B(const A&),因为前者的 cv-qualification 较少(如在复制初始化的情况下),如果我们使用 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();。