Ответ 1
TL;DR; Код плохо сформирован, MSVC ошибочно принимает его. Копирование-инициализация отличается от прямой инициализации. Объяснение непрофессионала заключается в том, что инициализация yoursBreaks
будет включать в себя два пользовательских преобразования (MyString --> const char* --> YourString
), тогда как прямая инициализация включает одно пользовательское преобразование (MyString --> const char*
), и вам разрешено не более одного определяемого пользователем преобразование. Стандартное объяснение, которое применяет это правило, заключается в том, что [over.best.ics] не разрешает определяемые пользователем преобразования в контексте инициализации копирования типа класса из несвязанного типа класса путем преобразования конструктора.
К стандарту! Что делает:
YourString yoursBreaks = mys;
означает? Каждый раз, когда мы объявляем переменную, это какая-то инициализация. В этом случае, согласно [dcl.init],
Инициализация, которая встречается в форме
=
элемента-элемента или условия (6.4), а также при передаче аргумента, возвращает функцию, исключая исключение (15.1), обрабатывая исключение (15.3 ) и инициализация агрегатного члена (8.6.1), называется копирование-инициализация.
Копирование-инициализация - это что-то вроде формы T var = expr;
Несмотря на появление =
, это никогда не вызывает operator=
. Мы всегда проходим через конструктор или функцию преобразования.
В частности, этот случай:
Если тип назначения является (возможно, cv-квалифицированным) типом класса:
- Если выражение инициализатора является prvalue, а cv-неквалифицированная версия типа источника одинакова класс как класс места назначения, [...]
- В противном случае, если инициализация является прямой инициализацией или если она является инициализацией копирования, cv-неквалифицированная версия типа источника - это тот же класс, что и производный класс класса пункт назначения, [...]
- В противном случае (то есть для остальных случаев инициализации копии) пользовательские последовательности преобразования, которые может конвертировать из типа источника в тип назначения или (когда используется функция преобразования), чтобы их производный класс перечисляется, как описано в 13.3.1.4, и наилучший выбирается посредством (13.3). Если преобразование не может быть выполнено или является неоднозначным, инициализация плохо сформирован.
Мы попадаем в эту последнюю пулю. Пусть перескакивает в 13.3.1.4:
- Конструкторы преобразования (12.3.1) из T являются кандидатными функциями.
- Когда тип выражения инициализатора является типом класса "cvS
", рассматриваются неявные функции преобразованияS
и его базовые классы. При инициализации временной привязки к первому параметру конструктора, где параметр имеет тип "ссылка на возможно cv-квалифицированныйT
", и конструктор вызывается с единственным аргументом в контексте прямой инициализации объекта типа "cv2T
", также рассматриваются явные функции преобразования. Те, которые не скрыты внутриS
, и выдают тип, чья неквалифицированная версия cv - это тот же тип, что иT
или является его производным классом, являются функциями-кандидатами. Функции преобразования, возвращающие "ссылка наX
", возвращают значения l или значения x, в зависимости от типа ссылаясь на типX
, и поэтому считается, что для этого процесса выбора кандидатских функций он даетX
.
Первая маркерная точка дает нам конструкторы преобразования YourString
, которые:
YourString(const char* );
Вторая пуля нам ничего не дает. MyString
не имеет функции преобразования, которая возвращает YourString
или тип класса, полученный из него.
Итак, ладно. У нас есть один конструктор-кандидат. Это жизнеспособно? [over.match] проверяет надежность через:
Тогда лучшая жизнеспособная функция выбирается на основе неявных последовательностей преобразования (13.3.3.1), необходимых для сопоставления каждого аргумента с соответствующим параметром каждой жизнеспособной функции.
и в [over.best.ics]:
Хорошо сформированная неявная последовательность преобразования является одной из следующих форм:
- стандартная последовательность преобразования (13.3.3.1.1),
- пользовательская последовательность преобразования (13.3.3.1.2), или
- последовательность преобразования многоточия (13.3.3.1.3).Однако, если цель - - первый параметр конструктора или
- неявный объект-параметр пользовательской функции преобразованияа конструктор или определяемая пользователем функция преобразования является кандидатом от
- 13.3.1.3, когда аргумент является временным на втором этапе инициализации копирования класса,
- 13.3.1.4, 13.3.1.5 или 13.3.1.6 (во всех случаях), или
- второй этап 13.3.1.7 [...]
пользовательские последовательности преобразований не учитываются. [Примечание. Эти правила предотвращают более чем одну определяемую пользователем преобразование от применения во время разрешения перегрузки, тем самым избегая бесконечной рекурсии. -end note] [Пример:struct Y { Y(int); }; struct A { operator int(); }; Y y1 = A(); // error: A::operator int() is not a candidate struct X { }; struct B { operator X(); }; B b; X x({b}); // error: B::operator X() is not a candidate
-end пример]
Итак, хотя существует последовательность преобразований от MyString
до const char*
, в этом случае она не рассматривается, поэтому этот конструктор не является жизнеспособным.
Поскольку у нас нет другого конструктора-кандидата, вызов плохо сформирован.
Другая строка:
YourString yoursAlsoWorks(mys);
называется прямой инициализацией. Мы вызываем во вторую маркерную точку из трех блоков [dcl.init], которые я цитировал ранее, которая в целом читает:
Соответствующие конструкторы перечислены (13.3.1.3), и лучший выбирается с помощью разрешения перегрузки (13.3). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или списком выражений в качестве аргумента (ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация плохо сформирована.
где 13.3.1.3 указывает, что конструкторы перечислены из:
Для прямой инициализации или инициализации по умолчанию, которая не находится в контексте инициализации копирования, все функции-кандидаты являются конструкторами класса инициализируемого объекта.
Эти конструкторы:
YourString(const char* ) // yours
YourString(YourString const& ) // implicit
YourString(YourString&& ) // implicit
Чтобы проверить жизнеспособность последних двух функций, мы повторно выполняем разрешение перегрузки из контекста инициализации копирования (который не соответствует указанному выше). Но для вашего YourString(const char*)
, это просто, существует жизнеспособная функция преобразования от MyString
до const char*
, поэтому она используется.
Обратите внимание, что здесь есть одно единственное преобразование: MyString --> const char*
. Одно преобразование в порядке.