重载 static_cast 的调用不明确

Call of overloaded static_cast is ambiguous

我有一些这样的代码

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,此代码不会产生任何编译错误。

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

a.operator B();

cppreference/direct_initialization 所述:

if the initializer is a prvalue expression whose type is the same class as T (ignoring cv-qualification), the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision (since C++17)

并且在 cppreference/copy_elision 中进一步阅读时,它说自 C++17 起,如果

,编译器需要省略复制和移动构造

In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.

因此,虽然 static_cast<B>(a); pre C++17 可以解释为 B(a.operator int())a.operator B(),但 C++17 必须选择第二个选项,因为 [= a.operator B() 的 33=] 值是 B 类型的 prvalue 并且它可以省略 copy-/move- 结构。

C++14

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

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion.

表达式 static_cast<B>(a) 对初始化程序 aB 类型的临时变量执行 direct-initialization。然后适用 N3797 [dcl.init] 第 17 段中的以下要点:

If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

和"applicable constructors"由N3797定义[over.match.ctor]:

For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized.

因此所有三个构造函数:B::B(int)B::B(const B&)B::B(B&&) 都是重载解析期间的候选对象。然后比较对应的三个隐式转换序列:A->intA->const B&A->B&&。结果,A->int 与其他两个区分开来,因为不满足 N3797 [over.ics.rank] 第 3 段中的以下项目符号的条件:

User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.

此外,在 N3797 [over.match.best] 第 1 段中没有确定最佳可行函数的特殊规则适用,因此重载决策不明确,这会导致编译器错误。

C++17

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

注意保证复制省略在这里不适用,因为它不是来自相同类型纯右值的初始化。

C++20 或更高版本

已经有 discussion 关于在这种 direct-initialization 情况下添加保证复制省略,因此选择 a.operator B() 的行为将来可能是合法的。