C 11 неявное преобразование

#c #c 11 #implicit-conversion

#c #c 11 #неявное преобразование

Вопрос:

 #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::stringamp; rStr = myStr; // Ok, but why?
}
  

Я использую VS 2013.

Вопросы:

  1. Почему определения str1 и str2 приводят к разным ошибкам компиляции?

  2. Как я знаю, при rStr создании сначала создается временный строковый объект, а затем rStr будет ссылаться на временный. Но почему создание временного объекта не приводит к ошибке компиляции? Есть ли какая-либо разница между tmp и strN ?

Комментарии:

1. Насколько я могу судить, вторая ошибка — это ошибка. g и clang принимают определение инициализацию str2 .

Ответ №1:

Первое определение std::string str1(myStr); действительно неоднозначно:

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

таким образом, эта инициализация завершается неудачно из-за двусмысленности.

По сути, это тот же сценарий, что и

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

foo(myStr); // ambiguous
  

Требуется ровно одно пользовательское преобразование, затем будет вызвана функция (для первого определения функция является конструктором). Оба преобразования являются жизнеспособными, и ни одно из них не является подмножеством другого, поэтому оба имеют одинаковый ранг.


Второе определение, std::string str2 = myStr; на самом деле, в порядке. Разрешено только одно определяемое пользователем преобразование в std::string , либо с помощью конструктора, либо с помощью функции преобразования, а не обоих. Так что только std::string str2 = myStr.operator std::string(); это жизнеспособно.

Обратите string str2 = expr; внимание, что когда expr не имеет типа string , требуется expr преобразовать в std::string . Полученный временный файл затем используется для инициализации str2 с помощью копирования / перемещения:

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

Следовательно, преобразование с правой стороны должно быть преобразовано непосредственно в std::string , в противном случае вам потребуется цепочка из двух определяемых пользователем преобразований для инициализации временного: (UDC = Определяемое пользователем преобразование)

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

Например, expr для char const* operator char* преобразования через, а затем std::string в конструктор преобразования требуется цепочка из двух определяемых пользователем преобразований => нежизнеспособно. Если мы попытаемся использовать operator char*() преобразование, нам понадобится дополнительный неявный вызов конструктора, чтобы сделать RHS a string .

Это отличается от string str1( expr ) , где expr не требуется неявное преобразование в string . Возможно, его придется преобразовать для инициализации параметра строкового конструктора. Прямая инициализация str1 из возможно преобразованного expr не является (n неявным) преобразованием, а просто вызовом функции. Никаких дополнительных временных не создается:

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

Это второе определение отклоняется при компиляции с включенным расширением языка. Без языковых расширений эта инициализация работает нормально в обновлении 2 VS2013.


Третий вариант следует другой схеме инициализации. Насколько я могу судить, в этом случае он должен вести себя как второй. Похоже, расширения языка применяются только ко второму, но не к третьему.

Комментарии:

1. Возможно , поведение расширения связано с этим поведением при присвоении a std::string .

2. Почему возникает двусмысленность в первом случае, не является ли предпочтительной не шаблонная функция, что позволяет избежать создания экземпляра шаблонного оператора?

3. @icepack Неоднозначность между шаблоном и шаблоном применяется только к выбору целевой функции, а не к преобразованиям AFAICT. Оба (все три, копирование перемещение) жизнеспособных конструктора не являются шаблонами.

4. Второе определение, есть ли что-то вроде этого, std::string str2(myStr->operator char*) или std::string str2(myStr->operator std::string);

5. @LeonhartSquall Извините, я не совсем понимаю, что вы имеете в виду. Я заменил синтаксис lax, который я использовал, чтобы показать, какие преобразования выполняются, на возможный менее неоднозначный вызов функции. Второе определение функционально эквивалентно std::string str2( myStr.operator std::string() ); . То есть временное std::string создается как возвращаемое значение функции преобразования; это временное затем перемещается в str2 .