Наследование конструктора и инициализация прямого члена
Я пытаюсь использовать комбинацию инициализации элемента прямых данных С++ 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
Ответы
Ответ 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)
, неявно объявленный наследующий конструктор