Подтвердить что ты не робот

Наследование конструктора и инициализация прямого члена

Я пытаюсь использовать комбинацию инициализации элемента прямых данных С++ 11 и синтаксиса "using" для наследования конструкторов базового класса. Теперь с gcc 5.4.0 (на Ubuntu 16.04) я наблюдаю странную ошибку, если тип элемента данных не имеет конструктора по умолчанию. Это, вероятно, проще всего понять, если посмотреть на следующий минимальный пример:

#include <iostream>

struct Foo {
  Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
};

struct Base {
  Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; }
};

struct Derived : public Base {
  using Base::Base;
  Foo foo{42};
};

int main() {
  Derived derived{120};
}

Этот код компилируется и выполняется с ожидаемым поведением с clang. С gcc он не компилируется, потому что компилятор удаляет конструктор Derived::Derived(int):

ttt.cpp: In function ‘int main()’:
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’
   Derived derived{120};
                      ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed:
   using Base::Base;
               ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’
ttt.cpp:4:3: note: candidate: Foo::Foo(int)
   Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
   ^
ttt.cpp:4:3: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&)
 struct Foo {
        ^
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&)
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided

Если я добавлю конструктор по умолчанию в Foo следующим образом:

  Foo() { std::cout << "Foo::Foo()" << std::endl; };

также gcc может скомпилировать его. Код ведет себя точно так же, в частности, добавленный конструктор по умолчанию Foo никогда не будет выполнен.

Итак, теперь мой вопрос: действительно ли это С++ 11? Если да, я, вероятно, нашел ошибку в gcc. В противном случае, не должны ли gcc и clang выдавать сообщение об ошибке, что это недопустимо С++ 11?

Редактировать после того, как на вопрос был приятно ответил @vlad-from-moscow: эта ошибка, похоже, присутствует и в gcc 6.2, поэтому я напишу отчет об ошибке.

2nd edit: Там уже есть ошибка, которую я не нашел в первом поиске: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

4b9b3361

Ответ 1

gcc не удовлетворяет стандарту С++. Унаследованный конструктор класса Derived должен вызвать конструктор Base в своем списке mem-initializer с аргументом, указанным для Derived унаследованного конструктора.

В стандарте С++ написано (12.9 Inheriting constructor)

8 Наследующий конструктор для класса неявно определяется, когда он используется odr (3.2) для создания объекта его типа класса (1.8). неявно определяемый наследующий конструктор выполняет набор инициализации класса, который будет выполняться с помощью написанного пользователем встроенный конструктор для этого класса с mem-initializer-list, чей только mem-initializer имеет mem-initializer-id, который называет базу класс, обозначенный в описании вложенного имени декларации использования и список выражений, указанный ниже, и где составной оператор в его теле функции пуст (12.6.2). Если это пользовательский конструктор будет плохо сформирован, программа плохо сформирован. Каждое выражение в списке выражений имеет вид static_cast (p), где p - имя соответствующего конструктор, а T - объявленный тип p.

Также в соответствии с разделом (12.6.2 Инициализация баз и членов)

8 В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначается идентификатором mem-initializer-id (включая если нет mem-initializer-list, потому что конструктор имеет noctor-initializer), и сущность не является виртуальным базовым классом абстрактный класс (10.4), то

- если объект является нестатическим элементом данных, который имеет Инициализатор с привязкой или выравниванием, объект инициализируется, как указано в 8.5;

Ответ 2

Похоже, вы правы, есть ошибка в gcc

Из §12.9 [class.inhctor]:

Использование-декларация (7.3.3), которая называет конструктор, неявно объявляет набор наследующих конструкторов. набор кандидатов унаследованных конструкторов из класса X, названный в декларации использования, состоит из фактического конструкторы и условные конструкторы, которые являются результатом преобразования дефолтных параметров следующим образом:

  • все конструкторы без шаблона X

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

§13.3.1.3 Инициализация конструктором [over.match.ctor]

Когда объекты типа класса имеют прямую инициализацию (8.5) [...], разрешение перегрузки выбирает конструктор. Для прямой инициализации кандидат функции - все конструкторы класса инициализированного объекта.

Итак, должен был быть выбран конструктор Foo::Foo(int), который явно не был в gcc.


Один из вопросов, который я прочитал после этого: "Разве это приводит к удалению конструктора по умолчанию для Derived?" Ответ - нет.

Удобно, что в стандарте приведен пример ниже этого отрывка (я исключаю, что не нужно):

struct B1 {
   B1(int);
};

struct D1 : B1 {
   using B1::B1;
};

Набор конструкторов, присутствующих в D1, является [ Emphasis mine]

  • D1(), неявно объявленный конструктор по умолчанию, плохо сформированный, если используется
  • D1(const D1&), неявно объявленный конструктор копирования, а не наследуемый
  • D1(D1&&), неявно объявленный конструктор перемещения, не наследуемый
  • D1(int), неявно объявленный наследующий конструктор