Рассмотрим этот пример:
struct T { };
struct S {
operator T();
};
S s;
T t = s;
[dcl.init] приведет нас к [over.match.copy], который найдет функцию преобразования operator T(). Но на этом мы закончили или нам нужно вызвать T(T&& rhs), привязав rhs к возврату operator T() через [dcl.init.ref]? Есть ли различия в ответе на этот вопрос между С++ 11 и С++ 1z?
Ответ 1 · рейтинг 15 · T.C.
Это подпадает под [dcl.init]/17.6.3. , что довольно ясно показывает, что происходит после того, как разрешение перегрузки выбирает функцию преобразования:
Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов является значением prvalue неквалифицированной cv версии целевого типа, объект результата которого инициализируется конструктором. Вызов используется для прямой инициализации в соответствии с приведенными выше правилами объекта, который является местом назначения инициализации копирования.
В вашем случае это, в свою очередь, превращается в [dcl.init]/17.6. 1а>:
Если выражением инициализатора является значение prvalue, а версия исходного типа без уточнения cv относится к тому же классу, что и класс назначения, выражение инициализатора используется для инициализации целевого объекта.
В C++11 второй шаг вызывает конструктор перемещения, так как он не имеет маркера, соответствующего 17.6.1 C++17. Вместо этого вы снова танцуете прямую инициализацию/разрешение перегрузки а>:
Если инициализация является прямой инициализацией, [...] рассматриваются конструкторы. Перечисляются применимые конструкторы ([over.match.ctor]), а лучший из них выбирается с помощью разрешения перегрузки ([over.match]). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или списком выражений в качестве аргумента(ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация имеет неправильный формат.
Этот ход можно (и на практике) исключить; см. [class.copy]/31.
На самом деле более интересный случай
T t(s);
который в соответствии с формулировкой C++17 фактически требуется для вызова конструктора перемещения, поскольку он использует правило прямой инициализации и разрешает перегрузку конструкторов T. Это выбирает конструктор перемещения T и вызывает его для инициализации t, преобразуя s в значение T prvalue, которое материализуется во временное и привязывается к параметру конструктора перемещения. Маркер 17.6.1 просто недоступен в процессе, а маркер в C++11 [class.copy]/31 (теперь [class.copy.elision]/1), допускавший исключение в этом сценарии, был удален в C++17.
Скорее всего это дефект.