Скопировать elision для прямой инициализации базового класса?

Следующий код не компилировать как с ССАГПЗ и Clang из - за копирования конструкции B базового класса suboject внутри A конструктора:

struct B{
  B();
  B(const B&) =delete;
  };

struct A:B{
  A():B(B()){} //=> error: use of deleted function...
  };

Тем не менее, согласно [class.base.init]/7:

Список выражений или braced-init-list в mem-initializer используется для инициализации обозначенного подобъекта (или, в случае делегирующего конструктора, полного объекта класса) в соответствии с правилами инициализации [dcl.init] для прямая инициализация.

Таким образом, правило инициализации одинаково для членов или прямых баз. Для подобъекта-члена Gcc и Clang не используют конструктор удаленных копий:

struct A2:B{
  B b;
  A2():b(B()){} //=> OK the result object of B() is b
  };

Разве это не ошибка компилятора Clang и Gcc? Не должен ли конструктор копирования B быть исключен в A()?


Интересно, что даже если gcc проверяет, правильно ли сформирована конструкция копии, он исключает этот вызов конструктора копирования, см. Сборку здесь.

Ответы

Ответ 1

Это действительно очень странно. Прежде всего, независимо от того, должен ли здесь вызываться конструктор копирования, поскольку [class.base.init]/7 не различает инициализацию базы и инициализацию члена, поведение должно быть одинаковым в обоих случаях. Я не могу найти никаких дополнительных формулировок в стандарте, которые каким-то образом вводили бы асимметрию между инициализацией базы против инициализации члена. Исходя из этого, я бы сказал, что мы можем заключить, что здесь, так или иначе, есть ошибка компилятора. ICC и MSVC, по крайней мере, ведут себя последовательно, когда дело доходит до инициализации базы против инициализации члена. Однако ICC и MSVC не согласны с тем, должен ли код быть принят или нет.

Я полагаю, что ответ на вопрос о том, следует ли вызывать конструктор копирования, можно найти, если мы снова посмотрим на [class.base.init]/7:

Список выражений или braced-init-list в mem-initializer используется для инициализации обозначенного подобъекта (или, в случае делегирующего конструктора, полного объекта класса) в соответствии с правилами инициализации [dcl.init] для прямая инициализация. [...]

Акцент мой. Я считаю, что релевантный бит [dcl.init] должен быть [dcl.init]/17.6, где мы находим:

  • Если тип назначения является (возможно, cv-квалифицированным) типом класса:

    • Если выражение инициализатора является prvalue, а версия cv-unqualified типа источника является тем же классом, что и класс назначения, выражение инициализатора используется для инициализации объекта назначения. [...]

    • В противном случае, если инициализация является прямой инициализацией, или если это инициализация копирования, где cv-неквалифицированная версия исходного типа является тем же классом или производным классом класса назначения, конструкторы рассматриваются. Применимые конструкторы перечислены ([over.match.ctor]), и лучший из них выбирается с помощью разрешения перегрузки ([over.match]). [...]

[...]

Если применить 17.6.2, это будет означать, что должен быть вызван конструктор копирования, что сделает MSVC единственным основным компилятором, который ведет себя правильно в этом примере. Тем не менее, моя интерпретация заключается в том, что 17.6.1 применяется в целом, а icc корректен, т.е. Ваш код должен компилироваться. Это означает, что у вас есть потенциально даже две ошибки в GCC и clang (инициализация базы и инициализация члена ведут себя по-разному + mem-initializer вызывает copy-ctor, хотя это не должно быть), и одна в MSVC…