Почему я не могу инициализировать структуру, полученную из другой структуры?

Когда я запускаю этот код:

struct X {
    int a;
};

struct Y : public X {};

X x = {0};
Y Y = {0};

Я получил:

error: could not convert ‘{0}’ from ‘<brace-enclosed initializer list>’ to ‘Y’

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


person Eric    schedule 07.06.2013    source источник


Ответы (2)


Ваша проблема связана с агрегатной инициализацией: struct X является агрегатом, а struct Y — нет. Вот стандартная цитата об агрегатах (8.5.1):

Агрегат представляет собой массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без инициализаторов с фигурными скобками или равными для нестатических элементов данных (9.2), без закрытых или защищенных нестатических элементов данных ( Раздел 11), без базовых классов (Статья 10) и без виртуальных функций (10.3).

Это предложение указывает, что если class имеет базовый класс, то это не агрегат. Здесь struct Y имеет struct X в качестве базового класса и, следовательно, не может быть агрегатным типом.

Что касается вашей конкретной проблемы, возьмите следующий пункт из стандарта:

Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся в качестве инициализаторов для членов агрегата в порядке возрастания индекса или членов. Каждый элемент инициализируется копированием из соответствующего предложения инициализатора. Если предложение-инициализатор является выражением, и для преобразования выражения требуется сужающее преобразование (8.5.4), программа имеет неправильный формат.

Когда вы делаете X x = {0}, агрегатная инициализация используется для инициализации a до 0. Однако когда вы делаете Y y = {0}, поскольку struct Y не является агрегатным типом, компилятор будет искать подходящий конструктор. Поскольку ни один из неявно сгенерированных конструкторов (по умолчанию, копирование и перемещение) не может ничего сделать с одним целым числом, компилятор отвергает ваш код.


Что касается этого поиска конструкторов, сообщения об ошибках от clang++ немного более явно указывают на то, что на самом деле пытается сделать компилятор (онлайн-пример):

Y Y = {0};
  ^   ~~~

main.cpp:5:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Y &' for 1st argument

struct Y : public X {};
       ^

main.cpp:5:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Y &&' for 1st argument

struct Y : public X {};
       ^

main.cpp:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided

Обратите внимание, что есть предложение расширить инициализацию агрегатов для поддержки ваш вариант использования, и он перешел в C++17. Если я правильно прочитал, это делает ваш пример действительным с ожидаемой семантикой. Итак... вам нужно только дождаться компилятора, совместимого с С++ 17.

person Morwenn    schedule 07.06.2013
comment
clang++ -std=c++1z версия 4.0.1 компилируется, g++ -std=c++17 версия 7.2.1 компилируется, но Visual Studio 2017 cl версии 19.12.25816 еще нет - person Patrick Fromberg; 04.12.2017
comment
Должно быть сейчас поддерживается с лета этого года в VS 2017 15.7 - person mirh; 04.01.2019

В C++17 и более поздних версиях вы можете инициализировать производную структуру с помощью скобок.

Теперь ваш код компилируется (GodBolt).

Вы получаете предупреждение об использовании брансов. Итак, рекомендуемый способ инициализации Y:

Y y = { {0} };

скорее, чем;

Y y = { 0 };

Это связано с тем, что такие наследуемые структуры теперь считаются агрегатными типами (которые допускают агрегатную инициализацию). См. эту статью от 2015 года.

person einpoklum    schedule 18.09.2020