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
Tare 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
Tis a class type, and the cv-unqualified version of the type ofotheris notTor derived fromT, or ifTis non-class type, but the type ofotheris a class type, user-defined conversion sequences that can convert from the type ofothertoT(or to a type derived fromTifTis 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