Запрещает ли C++17 копировать elision в случае, когда C++14 разрешил это?
рассмотрим следующее:
struct X {
X() {}
X(X&&) { puts("move"); }
};
X x = X();
в C++14 перемещение может быть устранено, несмотря на то, что конструктор перемещения имеет побочные эффекты благодаря [class.copy] / 31,
это выделение операций копирования / перемещения ... допускается при следующих обстоятельствах ... когда временный объект класса, который не был привязан к ссылке (12.2) будут скопированы/перемещены на объект класса с тем же резюме-неквалифицированный тип
в C++17 это пуля была извлечена. Вместо этого движение гарантированно будет устранено благодаря [dcl.init] / 17.6.1:
если выражение инициализатора является prvalue, а CV-неквалифицированная версия типа источника одинакова класс как класс назначения, выражение инициализатора используется для инициализации назначения объект. [ пример:
T x = T(T(T()));
называетT
конструктор по умолчанию для инициализацииx
. - конец пример ]
до сих пор факты, которые я изложил, хорошо известны. Но теперь давайте изменим код так, чтобы он читался:
X x({});
в C++14 выполняется разрешение перегрузки и {}
преобразуется во временное типа X
используя конструктор по умолчанию, затем переместился в x
. Правила copy elision позволяют избежать этого перемещения.
в C++17 разрешение перегрузки то же самое, но теперь [dcl.init] / 17.6.1 не применяется, и пуля из C++14 не является там больше нет. Нет выражения инициализатора, так как инициализатор является braced-init-list. Вместо этого кажется, что [dcl.init] / (17.6.2) применяется:
в противном случае, если инициализация является прямой инициализацией или если это инициализация копирования, где cv-неквалифицированная версия исходного типа является тем же классом, что и производный класс класса рассматриваются назначения, конструкторы. Перечислены применимые конструкторы (16.3.1.3), и лучшей одно выбрано через разрешение перегрузки (16.3). Конструктор выбрано так называется для инициализации объекта в качестве аргумента(ов) используется выражение инициализатора или список выражений. Если нет конструктор применяется, или разрешение перегрузки неоднозначно, инициализация плохо сформирована.
это, похоже, требует вызова конструктора перемещения, и если в другом месте стандарта есть правило, которое говорит, что это нормально, чтобы удалить его, я не знаю, где он находится.
1 ответов
как указывает T. C., Это похоже на CWG 2327:
рассмотрим такой пример, как:
struct Cat {}; struct Dog { operator Cat(); }; Dog d; Cat c(d);
это идет на 11.6 [dcl.init] пуля 17.6.2:
в противном случае, если инициализация является прямой инициализацией или если это инициализация копирования, где CV-неквалифицированная версия исходного типа является тем же классом, что и производный класс, класс назначения, конструкторы продуманный. Перечислены применимые конструкторы (16.3.1.3 [over.спичка.ctor]), а лучший выбирается через разрешение перегрузки (16.3 [over.спичка.)] Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или expression-list в качестве аргумента(ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация формируется неправильно.
разрешение перегрузки выбирает конструктор перемещения кота. Инициализация
Cat&&
параметр конструктора приводит к временному, в 11.6.3 [dcl.в этом.ref] пуля 5.2.1.2. Это исключает possitiblity из Элизии копию для этого случая.это, похоже, является упущением в изменении формулировки для гарантированного копирования elision. Мы должны предположительно одновременно рассматривать как конструкторы, так и функции преобразования в этом случае, как и для инициализации копирования, но нам нужно убедиться, что это не вводит никаких новые проблемы или неясности.
что делает эту же основную проблему тем, что у нас есть инициализатор (в OP,{}
, например, d
) это неправильный тип - нам нужно преобразовать его в правильный тип (X
или Cat
), но чтобы выяснить, как это сделать, нам нужно выполнить разрешение перегрузки. Это уже возвращает нас к конструктору move-где мы привязываем этот ссылочный параметр rvalue к новому объекту, который мы только что создали сделать это случиться. На данный момент уже слишком поздно что-либо менять. Мы уже там. Мы не можем... резервное копирование, ctrl-z, прервать прервать, хорошо начать заново.
как я уже упоминал в комментариях, я не уверен, что это было иначе в C++14. Для того, чтобы оценить X x({})
, мы должны построить X
что мы привязываемся к ссылочному параметру rvalue конструктора перемещения - мы не можем отменить перемещение в этот момент, привязка ссылки происходит до того, как мы даже знаем, что делаем движение.