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.
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.
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.
According to N3797 [expr.static.cast] paragraph 4:
An expression
e
can be explicitly converted to a typeT
using astatic_cast
of the formstatic_cast<T>(e)
if the declarationT t(e);
is well-formed, for some invented temporary variablet
(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 sequenceU2
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 ofU2
.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With