Запрещает ли C ++ 17 исключение копирования в случае, если это разрешено в C ++ 14?

Учтите следующее:

struct X {
    X() {}
    X(X&&) { puts("move"); }
};
X x = X();

В C ++ 14 перемещение можно было опустить, несмотря на то, что конструктор перемещения имеет побочные эффекты благодаря [class.copy] / 31,

Это исключение операций копирования / перемещения ... разрешено в следующих обстоятельствах ... когда временный объект класса, который не был привязан к ссылке (12.2), будет скопирован / перемещен в объект класса с тем же cv-unqualified тип

В C ++ 17 этот пункт был удален. Вместо этого перемещение гарантированно будет пропущено благодаря [dcl.init] /17.6.1:

Если выражение инициализатора является prvalue, а cv-неквалифицированная версия исходного типа является тем же классом, что и класс назначения, выражение инициализатора используется для инициализации целевого объекта. [Пример: T x = T(T(T())); вызывает T конструктор по умолчанию для инициализации x. - конечный пример]

Пока что факты, о которых я говорил, хорошо известны. Но теперь давайте изменим код так, чтобы он читался так:

X x({});

В C ++ 14 выполняется разрешение перегрузки, и {} преобразуется во временный тип X с использованием конструктора по умолчанию, а затем перемещается в x. Правила исключения копий позволяют опустить этот ход.

В C ++ 17 разрешение перегрузки такое же, но теперь [dcl.init] /17.6.1 не применяется, и маркера из C ++ 14 больше нет. Выражения инициализатора не существует, поскольку инициализатор - это список инициализации в фигурных скобках. Вместо этого похоже, что применяется [dcl.init] / (17.6.2):

В противном случае, если инициализация является прямой инициализацией, или если это инициализация копированием, где cv-неквалифицированная версия исходного типа является тем же классом, что и класс назначения, или производным классом, рассматриваются конструкторы. Применимые конструкторы перечислены (16.3.1.3), и лучший из них выбирается посредством разрешения перегрузки (16.3). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или списком выражений в качестве аргумента (ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация сформирована неправильно.

Похоже, что для этого требуется вызов конструктора перемещения, и если где-то в стандарте есть правило, в котором говорится, что его можно исключить, я не знаю, где он находится.


person Brian Bi    schedule 08.02.2018    source источник
comment
Это еще один вариант Core issue 2327.   -  person T.C.    schedule 08.02.2018
comment
@ T.C. Я не могу найти эту проблему в общедоступном списке   -  person Brian Bi    schedule 08.02.2018
comment
Я не уверен в вашем выводе по C ++ 14. {} преобразуется во временный объект типа _2 _..., который затем привязывается к ссылке из конструктора перемещения. Я не думаю, что ваша первоначальная цитата применима.   -  person Barry    schedule 08.02.2018
comment
@Barry, если переезд будет отменен, то привязки ссылок, конечно же, не будет.   -  person Brian Bi    schedule 09.02.2018
comment
@Brian Это предполагает антецедент. Если есть привязка, ее нельзя исключить, поэтому предположить, что привязки нет, потому что она пропущена, немного.   -  person Yakk - Adam Nevraumont    schedule 09.02.2018
comment
@Yakk Мне кажется, что единственная возможная интерпретация состоит в том, что копирование / перемещение можно исключить, если источник не уже привязан к ссылке. Если только удаляемая копия / перемещение требует привязки ее к ссылке, то ее можно исключить. С вашей интерпретацией никакого исключения было бы невозможно.   -  person Brian Bi    schedule 09.02.2018
comment
ИМО, теперь с формулировкой C ++ 17 ясно, что этот шаг должен произойти. В C ++ 14 у нас была своего рода ласковая формулировка, потому что движение определялось как инициализация (12.1, 8.5), в том числе для передачи аргументов функции. Я вижу только инициализации X {} и X&& временным X (или {}, опять же ласковой формулировкой). Возможно, нигде нет X, инициализированного временным элементом.   -  person Johannes Schaub - litb    schedule 01.03.2018


Ответы (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.match.ctor]), и лучший из них выбирается путем разрешения перегрузки (16.3 [over.match]). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или expression-list в качестве его аргумента (ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация сформирована неправильно.

Разрешение перегрузки выбирает конструктор перемещения Cat. Инициализация параметра Cat&& конструктора приводит к временному, согласно 11.6.3 [dcl.init.ref] подпункту 5.2.1.2. Это исключает возможность исключения копии в данном случае.

Похоже, это недосмотр в изменении формулировки для гарантированного копирования. По-видимому, в этом случае мы должны одновременно рассматривать и конструкторы, и функции преобразования, как и при инициализации копирования, но нам нужно убедиться, что это не создает каких-либо новых проблем или двусмысленностей.

Основная проблема в том, что у нас есть инициализатор (в OP {}, в этом примере d) неправильного типа - нам нужно преобразовать его в правильный тип (X или Cat), но чтобы выяснить, как для этого нам нужно выполнить разрешение перегрузки. Это уже приводит нас к конструктору перемещения - где мы привязываем этот ссылочный параметр rvalue к новому объекту, который мы только что создали, чтобы это произошло. На этом этапе отменять ход уже поздно. Мы уже там. Мы не можем ... вернуться назад, ctrl-z, abort abort, хорошо начать заново.

Как я уже упоминал в комментариях, я не уверен, что в C ++ 14 все было иначе. Чтобы оценить X x({}), мы должны построить X, который мы привязываем к параметру ссылки rvalue конструктора перемещения - мы не можем исключить перемещение в этой точке, привязка ссылки происходит еще до того, как мы узнаем, что делаем движение.

person Barry    schedule 08.02.2018