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

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

вопросы:

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

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

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; на самом деле штраф в размере. Только одно пользовательское преобразование to std::string разрешено, либо через конструктор, либо через функцию преобразования, а не оба. Так только std::string str2 = myStr.operator std::string(); жизнеспособна.

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

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

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

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

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

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


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