Инициализация массива std::array структурами

Хотел вот так инициализировать массив:

#include <string>
#include <array>

struct r{
        std::string name;
        int x;
};

int main()
{
        std::array<r, 2> a { {"weklrj", 3}, {"lekw", 2} };
}

Но выдает вот такую ошибку:

test.cpp: In function ‘int main()’:
test.cpp:12:50: error: too many initializers for ‘std::array<r, 2>’
   12 |  std::array<r, 2> a { {"weklrj", 3}, {"lekw", 2} };
      |                                                  ^

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

#include <string>
#include <array>

struct r{
        std::string name;
        int x;
};

int main()
{
        std::array<r, 2> a {{ {"weklrj", 3}, {"lekw", 2} }};
}

Вопрос: для чего нужны эти дополнительные скобки? Ведь по логике в первом варианте внешние - это uniform-инициализация, а внутренние - обозначения каждой отдельной структуры и все должно работать.


Ответы (3 шт):

Автор решения: Павел Ериков

Когда вы пишите

{ "строка", 3 } 

вы инициализируете структуру, а конкретней присваиваете её полям name и x значения "строка" и 3.

Когда же вы пишите:

array<r,2> a { элемент1, элемент2 }

Это выглядит таким образом: у структуры array как будто есть 2 поля, которые вы инициализируете элемент1 и элемент2, что не является правдой.

В исходном коде структуры array есть такая строчка:

value_type _M_instance[_Nm ? _Nm : 1];

где value_type это в вашем случае структура r, _Nm это размер 2. То есть вы добавляя {} просто инициализируете этот массив.

→ Ссылка
Автор решения: HolyBlackCat

std::array<T, N> - это класс, с одним публичным полем вида T некоторое_имя[N]; (по крайней мере для N > 0).

Поэтому, в std::array<r, 2> a {{ {"weklrj", 3}, {"lekw", 2} }};, внешние скобки - для всего класса std::array, следующие скобки - для этого массива, и две пары внутренних скобок - для элементов этого массива.


Интереснее другое. Почему std::array<int, 3> arr{1, 2, 3}; работает с одной парой скобок?

Cppreference говорит вот что:

The braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.

Говоря простым языком, убирать скобки можно только начиная с самых вложенных скобок.

Пример: если у нас есть

std::array<r, 2> a{{{"weklrj", 3}, {"lekw", 2}}};

То первым шагом мы можем убрать только самые вложенные пары скобок. Получаем:

std::array<r, 2> a{{"weklrj", 3, "lekw", 2}};

Дальше мы снова можем убрать самые вложенные скобки:

std::array<r, 2> a{"weklrj", 3, "lekw", 2};

Но, конечно, такое убирание скобок работает только если в середине не пропущены инициализаторы. Например, std::array<r, 2> a{{ {"weklrj"}, {"lekw"} }}; компилируется, а std::array<r, 2> a{{"weklrj", "lekw"}}; уже нет, потому что компилятор будет пытаться преобразовать вторую строку к intу.

→ Ссылка
Автор решения: AlexGlebe

Массив std :: array реализован как структура. У него есть только два конструктора. По умолчанию и фактического.

array<type,n> a;
array<type,n> a = { x , y } ;

Много фигурных скобок затрудняют компилятору определить представленные типы автоматически. Запись с фактическим типом r

std::array<r, 2> a { r {"weklrj", 3},  r {"lekw", 2} };

помогает компилятору определить тип r и он вызывает конструктор "aggregate-инициализации".

А также вызов конструктора с одним аргументом

std::array<r, 2> a { { {"weklrj", 3},  {"lekw", 2} } } ;

автоматически даёт один путь к вызову конструктора "aggregate-инициализации".

Вызов конструктора с элементами из структур неуказанного типа

std::array<r, 2> a { {"weklrj", 3}, {"lekw", 2} };

приводит к неопределённости : если это нужно делать "aggregate-инициализацию", то аргументов слишком много. А так как перед фигурными скобками не указан тип, то определить, что это структура r или другая невозможно.

→ Ссылка