Noexcept, наследование конструкторов и недопустимое использование неполного типа, который фактически завершен

Я не уверен, что это ошибка компилятора GCC или предполагаемое поведение noexcept.
Рассмотрим следующий пример:

struct B {
    B(int) noexcept { }
    virtual void f() = 0;
};

struct D: public B {
    using B::B;
    D() noexcept(noexcept(D{42})): B{42} { }
    void f() override { }
};

int main() {
    B *b = new D{};
}

Если элемент noexcept удален, он компилируется.
Во всяком случае, как и в примере, я получил эту ошибку от GCC v5.3.1:

test.cpp:8:31: error: invalid use of incomplete type ‘struct D’
     D() noexcept(noexcept(D{42})): B{42} { }
                               ^

Насколько я знаю, struct D не является неполным типом, но в инструкции участвуют наследующие конструкторы, и похоже, что компилятор действительно рассматривает полноту базовой структуры B больше, чем D.

Это предполагаемое поведение или это юридический код?

Для ясности:

  • здесь компиляция удалась с помощью clang 3.7.1
  • здесь компиляция не выполняется с использованием GCC 5.3.0

Подробнее см. эту ссылку в bugzilla для компилятора GCC.
В настоящее время ошибка по-прежнему не подтверждена. Я обновлю вопрос как можно скорее.

Ответы

Ответ 1

Ваш код является законным, хотя GCC заявляет об ином. В этом забавно выглядящем заявлении обижается:

D() noexcept(noexcept(D{42}));

Самый внешний noexcept является noexcept specifier, заявляя, что D::D() не является исключением, если и только если его аргумент константного выражения оценивается как true. Внутренний noexcept является noexcept operator, который проверяет во время компиляции, не выражает ли его выражение аргумента, которое фактически не оценивается, исключений. Поскольку D::D(int) не является исключением (унаследовано от B), это должно быть правдой.

cppreference.com явно отмечает, что использование оператора внутри спецификатора разрешено (выделено мной):

Оператор noexcept выполняет проверку времени компиляции, которая возвращает значение true, если объявлено, что выражение не выбрасывает никаких исключений.

Он может использоваться внутри шаблона функции noexcept, чтобы объявить, что функция будет генерировать исключения для некоторых типов, но не для других.

Теперь класс следует считать полным в спецификаторе noexcept в соответствии с §9.2.2 Стандарта (выделено жирным шрифтом):

Класс считается полностью определенным типом объекта (3.9) (или полным типом) при закрытии } спецификатора класса. В классе класса-члена класс рассматривается как полный внутри тела функций, аргументы по умолчанию, using-declarations, вводящие наследующие конструкторы (12.9), спецификации исключений и скобки -или-равные-инициализаторы для нестатических членов данных (включая такие вещи во вложенных классах). В противном случае он считается неполным в пределах своей спецификации класса.

В §15.4.1 определяется спецификация исключения как следующая грамматика:

исключение-спецификации:

  • динамических исключения спецификация

  • noexcept-спецификации

Поэтому GCC не должен отклонять ваш код.