Вызывается ли конструктор перемещения после вызова функции преобразования?

Рассмотрим этот пример:

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?


person Barry    schedule 01.06.2017    source источник
comment
Я считаю, что для инициализации копирования требуется наличие доступного конструктора копирования (для T), но он будет использовать конструктор перемещения, если он доступен (или полностью исключить копию, в зависимости от реализации operator T() он может применить NRVO).   -  person cdhowie    schedule 01.06.2017


Ответы (1)


Это подпадает под [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.

Скорее всего это дефект.

person T.C.    schedule 01.06.2017
comment
Можно поподробнее о последней части? Почему 17.6.1 не применяется одинаково в обоих случаях? - person Barry; 01.06.2017
comment
@Барри Уточнил. - person T.C.; 01.06.2017
comment
Большое спасибо. Я, например, с нетерпением жду статьи, предлагающей упростить инициализацию... - person Barry; 01.06.2017
comment
@Барри Разве мы не все? Учитывая, что здесь, на SO, уже есть несколько сообщений об инициализации, некоторые из них даже включены в FAQ по C++, что IMO намного слишком много. - person Passer By; 01.06.2017
comment
Разрешит ли С++ 11 исключение здесь? Поскольку инициализация происходит от объекта S к объекту T и от объекта T prvalue к ссылке T &&, но никогда от объекта T prvalue к объекту T, я пока не понимаю, как C++11 разрешил перемещение быть опущены либо. - person Johannes Schaub - litb; 05.06.2017
comment
Обратите внимание, что копирование и перемещение определяется как Объект класса может быть скопирован или перемещен двумя способами: путем инициализации ([class.ctor], [dcl.init]), в том числе для передачи аргумента функции ([expr.call]) и для возврат значения функции; и по заданию. . Таким образом, чтобы разрешить исключение в С++ 11, вам нужно было бы переместить объект T (временное значение prvalue) в другой объект T, чего я не вижу здесь. - person Johannes Schaub - litb; 05.06.2017
comment
Следовательно, строго говоря, перемещение объекта T в другой объект T не происходит, а просто вызывается конструктор перемещения T с объектом T в качестве аргумента (вроде как результат определенной пользователем последовательности преобразования на s). Это может показаться мелочью, но, по-видимому, имеет значительные последствия. - person Johannes Schaub - litb; 05.06.2017
comment
@JohannesSchaub-litb Ваше тонкое чтение не согласуется с каждым реализатором AFAICS, что убедительно свидетельствует о том, что это не предполагаемое чтение :) - person T.C.; 05.06.2017