Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call of overloaded static_cast is ambiguous

I have some code like this

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;
}

It produces such compilation error:

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&&)

I don't ask how to fix this. Just want to understand why compiler doesn't take it? From my POV static_cast<B>(a) is equal with a.operator B() but seems compilator reads it differently.

Update:

This behavior happens pre c++17. With c++17 this code doesn't produce any compilation error.

like image 951
Ivan Romanov Avatar asked Feb 21 '18 20:02

Ivan Romanov


People also ask

How do you fix ambiguous call of overloading?

There are two ways to resolve this ambiguity: Typecast char to float. Remove either one of the ambiguity generating functions float or double and add overloaded function with an int type parameter.

What does a Static_cast do?

The static_cast operator converts variable j to type float . This allows the compiler to generate a division with an answer of type float . All static_cast operators resolve at compile time and do not remove any const or volatile modifiers.


2 Answers

C++14

According to N3797 [expr.static.cast] paragraph 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.

The expression static_cast<B>(a) performs a direct-initialization of a temporary variable of type B from the initializer a. Then the following bullet from N3797 [dcl.init] paragraph 17 applies:

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.

And "applicable constructors" are defined by N3797 [over.match.ctor]:

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

So all the three constructors: B::B(int), B::B(const B&), B::B(B&&) are candidates during overload resolution. Then three corresponding implicit conversion sequences: A->int, A->const B&, A->B&& are compared. As a result, A->int is distinguishable from the other two since the conditions of the following bullet from N3797 [over.ics.rank] paragraph 3 are not met:

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.

In addition, no special rules of determining the best viable function in N3797 [over.match.best] paragraph 1 apply, so the overload resolution is ambiguous, which causes the compiler error.

C++17

The deduction above also holds, so this is a compiler bug (for now). The wording for [expr.static.cast] paragraph 4 has changed due to CWG 242, but it does not affect our conclusion.

Note guaranteed copy elision does not apply here because it is not an initialization from a prvalue of the same type.

C++20 or later

There is already a discussion about adding guaranteed copy elision to such direct-initialization cases, so maybe the behavior of selecting a.operator B() will be legal in the future.

like image 73
xskxzr Avatar answered Oct 10 '22 05:10

xskxzr


This is because the rules of direct initialization (wrt copy elision in particular) are slightly altered in C++17, and static_cast<B>(a); simply results in:

a.operator B();

As mentioned on 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)

And when reading further in cppreference/copy_elision, it says that since C++17, the compilers are required to omit the copy- and move- construction if

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.

Thus, while static_cast<B>(a); pre C++17 can be interpreted both as B(a.operator int()) and a.operator B(), C++17 has to pick the second option, since the return value of a.operator B() is prvalue of type B and it can omit copy-/move- construction.

like image 39
nVxx Avatar answered Oct 10 '22 05:10

nVxx