Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 implicitly convert

#include <string>

struct String
{
    template<typename T> operator T*() { return 0; }
    operator std::string() { return ""; }
};

int main()
{
    String myStr;

    std::string str1(myStr); // ambiguous, error C2668

    std::string str2 = myStr; // error C2440:
    // 'initializing' : cannot convert from 'String' to
    // `std::basic_string<char,std::char_traits<char>,std::allocator<char>>',
    // No constructor could take the source type,
    // or constructor overload resolution was ambiguous

    const std::string& rStr = myStr; // Ok, but why?
}

I'm using VS 2013.

Questions:

  1. Why do the definitions of str1 and str2 lead to different compile errors?

  2. As I know, when rStr is created, a temporary string object is created firstly, then rStr will refer to the temporary. But, why does the creation of the temporary object not lead a compile error? Is there any different between tmp and strN?

like image 662
Leonhart Squall Avatar asked Jun 16 '14 10:06

Leonhart Squall


1 Answers

The first definition, std::string str1(myStr); is indeed ambigous:

std::string str1(myStr.operator char*());
// or
std::string str1(myStr.operator std::string());

so this initialization fails due to an ambiguity.

This is essentially the same scenario as

void foo(char const*);
void foo(std::string);

foo(myStr); // ambiguous

Exactly one user-defined conversion is required, then a function will be called (for the first definition, the function is a constructor). Both conversions are viable, and neither is a subset of the other, so both have the same rank.


The second definition, std::string str2 = myStr; is actually fine. Only one user-defined conversion to std::string is allowed, either via a constructor or via a conversion function, not both. So only std::string str2 = myStr.operator std::string(); is viable.

Note string str2 = expr; when expr is not of type string requires the expr to be converted to std::string. The resulting temporary is then used to initialize str2 via a copy/move:

string str2 = string(expr);
//            ~~~~~~ implicit

Therefore, the conversion on the right hand side must convert directly to std::string, otherwise you would need a chain of two user-defined conversions to initialize the temporary: (UDC = User-Defined Conversion)

string str2 = string(expr);
// resolved as:
string str2 = expr.operator string();        // fine: one implicit UDC
string str2 = string(expr.operator char*()); // error: two UDCs

For example, expr to char const* via the operator char* and then to a std::string via the converting constructor requires a chain of two user-defined conversions => not viable. If we try to use the operator char*() conversion, we need an additional constructor implicit constructor call to make the RHS a string.

This is different from string str1( expr ), where expr does not need to be converted implicitly to string. It might have to be converted to initialize a parameter of a string constructor. The direct initialization of str1 from the possibly converted expr is not a(n implicit) conversion itself, but just a function call. No extra temporary is created:

string str1( expr );
// resolved as:
string str1( expr.operator string() ); // fine
string str1( expr.operator char* () ); // fine

This second definition is refused when compiling with enabled language extension. Without language extensions, this initialization is fine in VS2013 Update 2.


The third one follows a different initialization scheme. It should behave like the second one in this case, as far as I can tell. The language extensions seems to apply only to the second one but not to the third one, it seems.

like image 167
dyp Avatar answered Nov 04 '22 08:11

dyp