【问题标题】:Since there are two ways to define a conversion in C++ how do they interact when there are two possibilities for the same conversion?由于有两种方法可以在 C++ 中定义转换,当同一转换有两种可能性时,它们如何交互?
【发布时间】:2014-03-16 23:16:27
【问题描述】:

我只是想了解 C++ 的工作原理,这并不是真正解决我的代码中的特定问题。

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

如果您是 A 的作者,您可以在 A 中添加类似这样的内容:

operator B() {
   // code
}

如果你是 B 的作者,你可以在 B 中添加这样的内容:

B(const A &a) {
    // code
}

如果我理解正确,其中任何一个都将允许 A 隐式转换为 B。那么,如果两者都定义了,则使用哪一个?这还允许吗?

注意:我了解您可能永远不应该处于这样做的情况。您要么使构造函数显式化,要么更可能只有两者之一。我只是想知道 C++ 规范是怎么说的,我不知道如何查找。

【问题讨论】:

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


    【解决方案1】:

    很遗憾,这个问题的答案可能比您所寻找的要复杂。正如Lightness Races in Orbit指出的那样,编译器确实会拒绝模棱两可的转换,但是转换是否模棱两可?让我们来看看几个案例。所有引用均指向 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)中的初始化相同

    因此,在每种情况下,使用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)

    长话短说,这意味着从AB 的用户定义转换序列中的用户定义转换本身会受到重载决议的影响; 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();

    【讨论】:

    【解决方案2】:

    [C++11: 12.3/2]: 用户定义的转换仅适用于明确的情况。 [..]

    12.3 继续列出您确定的两种类型。

    【讨论】:

    • 呃,话虽如此,this works。所以我真的不知道发生了什么。哎呀。
    • 我不确定那段想要说/指定什么。重载解析本身拒绝 [over.best.ics]/10 中的模棱两可的转换序列,因此它看起来是多余的(或者它意味着不同的东西)。无论如何,这里有一个模棱两可的例子:coliru.stacked-crooked.com/a/442bd80e5d1e7e64
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    相关资源
    最近更新 更多