调用重载的static_cast是不明确的

时间:2018-02-21 20:20:05

标签: c++ static-cast

我有一些像这样的代码

struct B
{
    B() {}
    B(int v) {}
};

struct A
{
    operator int() const { return 1; }
    operator B() const { return B(); }
};

int main()
{
    A a;
    static_cast<B>(a); // Error here
    a.operator B(); // This is OK
    return 0;
}

它会产生这样的编译错误:

main.cpp: In function ‘int main()’:
main.cpp:16:21: error: call of overloaded ‘B(A&)’ is ambiguous
     static_cast<B>(a);
                     ^
main.cpp:4:5: note: candidate: B::B(int)
     B(int v) {}
     ^
main.cpp:1:8: note: candidate: constexpr B::B(const B&)
 struct B
        ^
main.cpp:1:8: note: candidate: constexpr B::B(B&&)

我不知道如何解决这个问题。只想了解编译器为什么不接受它?从我的POV static_cast<B>(a)等于a.operator B(),但似乎编译器会以不同的方式读取它。

更新

此行为发生在c ++ 17之前。使用c ++ 17,此代码不会产生任何编译错误。

2 个答案:

答案 0 :(得分:2)

这是因为direct initialization(特别是wrt copy elision)的规则在C ++ 17中略有改变,static_cast<B>(a);只会导致:

a.operator B();

正如cppreference/direct_initialization所述:

  

如果初始化程序是一个prvalue表达式,其类型与T相同(忽略cv-qualification),则初始化程序表达式本身(而不是从中实现的临时表达式)用于初始化目标对象:请参阅copy elision(自C ++ 17)

当在cppreference/copy_elision中进一步阅读时,它表示自C ++ 17以来,编译器需要省略复制和移动构造,如果

  

在函数调用中,如果return语句的操作数是prvalue,并且函数的返回类型与该prvalue的类型相同。

因此,虽然static_cast<B>(a);预C ++ 17可以解释为B(a.operator int())a.operator B(),但C ++ 17必须选择第二个选项,因为{的返回值{ {1}}是a.operator B()类型的prvalue,它可以省略复制/移动构造。

答案 1 :(得分:2)

C ++ 14

根据N3797 [expr.static.cast]第4段:

  

如果声明e格式正确,则可以使用T形式的static_cast将表达式static_cast<T>(e)显式转换为T t(e);类型,对于一些发明的临时变量t(8.5)。这种显式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同。

表达式static_cast<B>(a)从初始化程序B执行a类型的临时变量的直接初始化。然后来自N3797 [dcl.init]第17段的以下子弹适用:

  

如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同,或者是派生类,则考虑构造函数。 枚举适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,使用初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策不明确,则初始化是不正确的。

和&#34;适用的构造者&#34;由N3797 [over.match.ctor]定义:

  

对于直接初始化,候选函数是正在初始化的对象类的所有构造函数。

因此,所有三个构造函数:B::B(int)B::B(const B&)B::B(B&&)在重载解析期间都是候选者。然后比较三个相应的隐式转换序列:A->intA->const B&A->B&&。因此,A->int可与其他两个区别开,因为N3797 [over.ics.rank]第3段中的以下子弹的条件未得到满足:

  

如果它们包含相同的用户定义转换函数或构造函数,则用户定义的转换序列U1是比其他用户定义的转换序列U2更好的转换序列它们在聚合初始化中初始化相同的类,在任何一种情况下都是第二个标准转换   U1的序列优于U2的第二标准转换序列。

此外,在N3797 [over.match.best]第1段中没有确定最佳可行功能的特殊规则,因此重载分辨率不明确,导致编译器错误。

C ++ 17

上面的推论也有,所以这是一个编译器错误(目前)。由于CWG 242,[expr.static.cast]第4段的措辞已经改变,但这并不影响我们的结论。

注意保证副本省略不适用于此处,因为它不是来自相同类型的prvalue的初始化。

C ++ 20或更高版本

已经有discussion关于为这种直接初始化案例添加有保证的副本省略,因此选择a.operator B()的行为将来可能是合法的。