Конструктор наследования С++ constexpr

Следующий код компилируется с GCC 8.2, но не с Clang 6.0.1:

// A struct named Foo.
struct Foo
{
  // Data member of type 'int'.
  int val;

  // Default constructor (constexpr).
  constexpr Foo() noexcept : val(0) {}
};

// A struct named Bar.
struct Bar : Foo
{
  // Make use of the constructors declared in Foo.
  using Foo::Foo;

  // A constructor taking an object of type Foo.
  // COMMENTING THIS CONSTRUCTOR SOLVE THE COMPILATION ISSUE.
  constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
};


// A struct named Test.
struct Test
{
  // Data member of type 'Bar'.
  Bar bar;

  // A defaulted default constructor.
  constexpr Test() noexcept = default;
};


// Main function.
int main() { return 0; }

Сообщение Clang с сообщением:

error: определение по умолчанию конструктора по умолчанию не является constexpr
constexpr Test() noexcept = default;

Я хотел бы понять, почему Кланг отвергает этот код.

Ответ 1

Похоже, что clang полагается на формулировку pre С++ 17 из раздела С++ 14 [class.inhctor] p3:

Для каждого конструктора без шаблона в наборе кандидатов унаследованных конструкторов, отличных от конструктора, не имеющего параметров или конструктора копирования/перемещения, имеющего единственный параметр, конструктор неявно объявляется с теми же характеристиками конструктора, если не существует конструктор, объявленный пользователем, с та же подпись в полном классе, где появляется объявление using, или конструктор будет по умолчанию, копирует или перемещает конструктор для этого класса. Аналогично, для каждого шаблона конструктора в наборе кандидатов наследуемых конструкторов шаблон конструктора неявно объявляется с теми же характеристиками конструктора, если в полном классе не существует эквивалентного объявленного пользователем шаблона конструктора ([temp.over.link]), где появляется декларация использования. [Примечание: аргументы по умолчанию не наследуются. Спецификация исключения подразумевается, как указано в [except.spec]. - конечная нота]

Итак, в С++ 14:

using Foo::Foo;

означает, что Bar не наследует конструктор по умолчанию Foo и Bar не имеет конструктора по умолчанию, поскольку он запрещен вашим выражением:

constexpr Bar(Foo const obj) noexcept : Foo(obj) {}

Добавление конструктора по умолчанию в Bar устраняет проблему, см. Его в прямом эфире:

constexpr Bar() = default ;

Формулировка была изменена в С++ 17 на бумагу p0136r1: Наследование наследовательных конструкторов (основная проблема 1941 и др.), Которая была видна, была принята из изменений между С++ 14 и С++ 17 DIS

Следующие документы были перенесены на заседания комитетов, но их содержание слишком специфично, чтобы вызывать отдельные функции: N3922, N4089, N4258, N4261, N4268, N4277, N4285, P0017R1, P0031R0, P0033R1, P0074R0, P0136R1, P0250R3, P0270R3, P0283R2, P0296R2, P0418R2, P0503R0, P0509R1, P0513R0, P0516R0, P0517R0, P0558R1, P0599R1, P0607R0, P0612R0

мы можем удалить [class.inhctor]:

Удалите 12.9 class.inhctor, "Наследование конструкторов".

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

Таким образом, похоже, что gcc здесь правильно, и у нас есть потенциальная ошибка clang.

Заметки о выпуске gcc 7

Мы также получаем диагностику в gcc pre 7.x(см. Ее в прямом эфире). Если мы посмотрим на примечания к выпуску gcc 7, мы увидим:

Семантика по умолчанию унаследованных конструкторов изменилась во всех режимах, следующих за P0136. По сути, разрешение перегрузки происходит так, как если бы вызывал наследуемый конструктор напрямую, а компилятор заполнял строительство других баз и элементов по мере необходимости. В большинстве применений не должно быть никаких изменений. Старое поведение можно восстановить с помощью -fno-new-inheriting-ctors или -fabi-version менее 11.

Кажется, что подтверждает первоначальный вывод. Если мы используем -fno-new-inheriting-ctors со слегка измененной версией вашей программы, он больше не компилирует, который поддерживает это изменение с помощью P0136.

Ответ 2

В С++ 14 конструкторы по умолчанию нельзя унаследовать.

§12.9 [class.inhctor] (акцент мой)

3 Для каждого конструктора без шаблона в наборе кандидатов наследуемых конструкторов, отличном от конструктора, не имеющего параметров, или конструктора копирования/перемещения, имеющего единственный параметр, конструктор неявно объявляется с теми же характеристиками конструктора, если не существует конструктор, объявленный пользователем с той же сигнатурой в полном классе, где появляется объявление using, или конструктор будет по умолчанию, копировать или перемещать конструктор для этого класса....

Это в основном означает, что для вашего класса Bar, ctor будет неявно определен - и означает using Foo::Foo не делает ничего значимого.

Однако, поскольку у вас есть отдельный конструктор для Bar, это предотвращает неявное определение конструктора по умолчанию.

Причина, по которой это работает, когда вы прокомментируете свою отдельную constexpr Bar(Foo const obj) ctor из-за

5 [Примечание: конструкторы по умолчанию и копирование/перемещение могут быть объявлены неявно, как указано в 12.1 и 12.8. -End note]

§12.1/5 [class.ctor]

... Если этот пользовательский конструктор по умолчанию будет удовлетворять требованиям конструктора constexpr (7.1.5), неявно определенный конструктор по умолчанию является constexpr....

Таким образом, неявно объявленный конструктор объявляется как constexpr, что заставляет ваш код работать и компилироваться, как ожидалось.

Вы можете устранить эту проблему, просто явно по умолчанию задав по умолчанию ctor следующим образом:

constexpr Bar() noexcept = default;

Вы также можете взглянуть на класс Constexpr: Inheritance?

Проблема там немного другая, но очень похожа на то, что вы видите.

К сожалению, я не могу найти соответствующие части в стандарте С++ 17. Я предполагаю, что рассуждение одно и то же, но не может найти ссылку на 100% уверенной.