由于有两种方法可以在C ++中定义转换,当同一转换有两种可能性时,它们如何相互作用?

时间:2014-03-16 23:16:28

标签: c++ implicit-conversion conversion-operator

我只是想澄清C ++是如何工作的,这并不是解决我代码中的特定问题。

在C ++中,你可以说类型A应该以两种不同的方式隐式转换为类型B.

如果您是A的作者,可以在A:

中添加这样的内容
operator B() {
   // code
}

如果您是B的作者,可以在B:

中添加这样的内容
B(const A &a) {
    // code
}

如果我理解正确的话,其中任何一个都会允许A隐式转换为B.所以如果两个都被定义使用了哪一个?这甚至是允许的吗?

注意:我知道您可能永远不会遇到这种情况。您可以使构造函数显式,也可以只使用两者中的一个。我只是想知道C ++规范说的是什么,我不知道如何看待它。

2 个答案:

答案 0 :(得分:1)

  

[C++11: 12.3/2]:用户定义的转化仅在明确无误的情况下应用。 [..]

12.3继续列出你确定的两种。

答案 1 :(得分:1)

不幸的是,这个问题的答案可能比你想要的更复杂。确实,编译器会拒绝模糊转换,因为Orbit中的Lightness Races指出,但转换是否含糊不清?我们来看几个案例。所有引用都是针对C ++ 11标准的。

显式转换

这并没有直接解决你的问题,因为你问过隐式转换,但是由于Orbit中的Lightness Races给出了一个显式转换的例子,我还是会讨论它。

在以下情况下,从AB进行明确转换:

  • 您使用语法(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)

简而言之,这意味着用户定义的从AB的转换序列中的用户定义转换本身会受到重载决策的影响; 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();