我只是想澄清C ++是如何工作的,这并不是解决我代码中的特定问题。
在C ++中,你可以说类型A应该以两种不同的方式隐式转换为类型B.
如果您是A的作者,可以在A:
中添加这样的内容operator B() {
// code
}
如果您是B的作者,可以在B:
中添加这样的内容B(const A &a) {
// code
}
如果我理解正确的话,其中任何一个都会允许A隐式转换为B.所以如果两个都被定义使用了哪一个?这甚至是允许的吗?
注意:我知道您可能永远不会遇到这种情况。您可以使构造函数显式,也可以只使用两者中的一个。我只是想知道C ++规范说的是什么,我不知道如何看待它。
答案 0 :(得分:1)
[C++11: 12.3/2]:
用户定义的转化仅在明确无误的情况下应用。 [..]
12.3继续列出你确定的两种。
答案 1 :(得分:1)
不幸的是,这个问题的答案可能比你想要的更复杂。确实,编译器会拒绝模糊转换,因为Orbit中的Lightness Races指出,但转换是否含糊不清?我们来看几个案例。所有引用都是针对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)中的初始化做同样的事情因此,在每种情况下,使用类型B
的值作为参数,对类型为A
的prvalue执行直接初始化。 §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资格(如在复制初始化的情况下),如果我们有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();
。