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.
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.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.