Унаследованные конструкторы игнорируют инициализацию в классе
У меня есть класс, определенный следующим образом:
class ASTConcatenateLiteral : public ASTExpr {
using ASTExpr::ASTExpr;
private:
Type type_ = Type::nothingness(); // Type does not have a default constructor
};
Это прекрасно работает с Кланом. GCC, однако, дает мне сообщение об ошибке, которое заставляет меня думать, что он пытается использовать инициализатор по умолчанию:
error: нет соответствующей функции для вызова в 'EmojicodeCompiler:: Type:: Type()
Если я заменил using ASTExpr::ASTExpr;
на публичный конструктор, подобный этому (ASTExpr предоставляет только этот конструктор)
ASTConcatenateLiteral(const SourcePosition &p) : ASTExpr(p) {}
все работает нормально.
Согласно cppreference.com:
Унаследованные конструкторы эквивалентны определяемым пользователем конструкторам с пустым телом и списком инициализаторов членов, состоящим из одного вложенного имени-спецификатора, который пересылает все его аргументы в конструктор базового класса.
Так почему же не работает наследуемый конструктор? Если унаследованный конструктор ведет себя как пользовательские конструкторы, он должен использовать значение, указанное для type_
, правильно? Какой из компиляторов прав в соответствии со стандартом?
Ответы
Ответ 1
Кланг верен. Когда используется наследуемый конструктор, инициализация должна выполняться так, как если бы для инициализации объекта производного класса использовался стандартный конструктор по умолчанию, поэтому инициализаторы элементов по умолчанию.
(акцент мой)
Если разрешение перегрузки выбирает один из наследуемых конструкторов при инициализации объекта такого производного класса, то базовый подобъект, из которого был унаследован конструктор, инициализируется с помощью наследуемого конструктора, а все остальные базы и члены Derived инициализируются так, как если бы по умолчанию конструктор по умолчанию ( инициализаторы элементов по умолчанию используются, если они предусмотрены, в противном случае выполняется инициализация по умолчанию).
Вот пример из стандарт:
struct B1 {
B1(int, ...) { }
};
struct B2 {
B2(double) { }
};
int get();
struct D1 : B1 {
using B1::B1; // inherits B1(int, ...)
int x;
int y = get();
};
void test() {
D1 d(2, 3, 4); // OK: B1 is initialized by calling B1(2, 3, 4),
// then d.x is default-initialized (no initialization is performed),
// then d.y is initialized by calling get()
D1 e; // error: D1 has a deleted default constructor
}
Обратите внимание, что d.y
инициализируется инициализатором элемента по умолчанию.
Ответ 2
Это было рассмотрено разработчиками GCC как ошибка компилятора, PR67054, которая была исправлена в GCC 7.2.
Их минимальный пример -
struct A
{
A(int) {}
};
struct C
{
C(int) {}
};
struct B : A
{
using A::A;
C c = 42;
};
int main()
{
B b = 24;
}
Онлайн-компиляторы могут использоваться для проверки соответствия GCC и clang.
Обратите внимание, что в GCC 7.1, насколько я могу судить, не будет создан неправильный код. Хотя компилятору нужен конструктор по умолчанию, этот конструктор по умолчанию никогда не будет вызван. Поэтому возможным решением для более старых версий GCC является объявление конструктора по умолчанию, но не определение. Возможно, даже используйте атрибут (не стандартный С++), чтобы отклонить любые попытки его использования:
struct C
{
#ifdef GCC_WORKAROUND_PR67054
C() __attribute__((__error__("No.")));
#endif
C(int) {}
};