#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.
Вопросы:
-
Почему определения
str1
иstr2
приводят к разным ошибкам компиляции? -
Как я знаю, при
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
.