I have a class C
which has a casting operator to anything. In the example I tried to cast an instance of it to std::string
in three different ways: static_cast
, constructor of std::string
and assigning to std::string
. However, only the last one compiles, while the others raise an error of ambiguous constructor.
The reason of the error is clear enough: there are many ways to convert C
to something which the constructor of std::string
can accept. But what is the difference between these cases? Why cast operator works as intended here but not there?
struct C {
template<typename T>
operator T() const {
return T{};
}
};
int main() {
C c;
cout << static_cast<string>(c) << endl; // compile error
string bad(c); // compile error
string ok = c; // compiles successfully
}
UPD: as bolov mentioned in comments, this issue doesn't reproduce with C++17. I tested it with g++-5 and clang-3.8 with -std=c++11 and -std=c++14, and it shows the described errors.
The main difference between these two types of initialization is that the copy initialization creates a separate memory block for the new object. But the direct initialization does not make new memory space. It uses reference variable to point to the previous memory block.
What is the difference between initialization and assignment? Initialization gives a variable an initial value at the point when it is created. Assignment gives a variable a value at some point after the variable is created.
Direct initialization applies when initializing objects without explicit assignment. It also is also used for explicit casts, whether function-style or via static_cast and applies to Lambda closure arguments captured by value (which may be regarded as a special case of member initialization).
In other words, a good compiler will not create a copy for copy-initialization when it can be avoided; instead it will just call the constructor directly -- ie, just like for direct-initialization.
Before C++17
static_cast<string>(c)
and string bad(c)
performs direct initialization, then
the constructors of
T
are examined and the best match is selected by overload resolution. The constructor is then called to initialize the object.
As you said, all the possible constructors of std::string
are examined and C
can be converted to anything required, then causes ambiguity.
string ok = c
performs copy initialization (note it's not assignment), then
If
T
is a class type, and the cv-unqualified version of the type ofother
is notT
or derived fromT
, or ifT
is non-class type, but the type ofother
is a class type, user-defined conversion sequences that can convert from the type ofother
toT
(or to a type derived fromT
ifT
is a class type and a conversion function is available) are examined and the best one is selected through overload resolution.
That means the conversion from C
to std::string
is examined, and used for the initialization here.
After C++17
Since C++17 for direct initlizatioin,
if the initializer is a prvalue expression whose cv-unqualified type is the same class as
T
, the initializer expression itself, rather that a temporary materialized from it, is used to initialize the destination object: see copy elision (since C++17)
That means the conversion from C
to std::string
is perferred and used for the initialization, then the ambiguity disappears and the code works well.
LIVE
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