Запрещает ли 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 конструктора перемещения - мы не можем отменить перемещение в этот момент, привязка ссылки происходит до того, как мы даже знаем, что делаем движение.