Инициализация std :: array & lt; & gt;

Рассмотрим следующий код:

#include <array>

struct A
{
int a;
int b;
};

static std::array<A, 4> x1 =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};

static std::array<A, 4> x2 =
{
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
}
};

static std::array<A, 4> x3 =
{
A{ 1, 2 },
A{ 3, 4 },
A{ 5, 6 },
A{ 7, 8 }
};

static std::array<A, 4> x4 =
{
A{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};

Компиляция с gcc:

$ gcc -c --std=c++11 array.cpp
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’
};
^
$

NB1: комментируя первый оператор инициализации, код компилируется без ошибок.
NB2: Преобразование всей инициализации в вызовы конструктора дает одинаковые результаты.
NB3: MSVC2015 ведет себя так же.

Я понимаю, почему первая инициализация не компилируется, и почему вторая и третья в порядке. (например, см. C ++ 11: правильная инициализация std :: array?.)

Мой вопрос: почему именно компилируется финальная инициализация?

15

Решение

Краткая версия: предложение инициализатора, которое начинается с { останавливает скобку Это имеет место в первом примере с {1,2}, но не в третьем или четвертом, которые используют A{1,2}, Brace-elision использует следующие N предложений инициализатора (где N зависит от агрегата, который должен быть инициализирован), поэтому только первое предложение инициализатора N не должно начинаться с {,


Во всех реализациях стандартной библиотеки C ++, о которых я знаю, std::array это структура, которая содержит массив в стиле C. То есть у вас есть агрегат, который содержит суб-агрегат, так же, как

template<typename T, std::size_t N>
struct array
{
T __arr[N]; // don't access this directly!
};

При инициализации std::array из приготовился-INIT-лист, поэтому вам придется инициализировать членов содержащий массив. Следовательно, в этих реализациях явная форма:

std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }};

Внешний набор скобок относится к std::array структура; второй набор скобок относится к вложенному массиву в стиле C.


C ++ позволяет при инициализации агрегата пропускать определенные скобки при инициализации вложенных агрегатов. Например:

struct outer {
struct inner {
int i;
};
inner x;
};

outer e = { { 42 } };  // explicit braces
outer o = {   42   };  // with brace-elision

Правила заключаются в следующем (используется черновик после N4527, то есть после C ++ 14, но в C ++ 11 все равно есть дефект, связанный с этим):

Брекеты могут быть исключены в инициализатора-лист следующее. Если
инициализатора-лист начинается с левой скобки, затем последующая
разделенный запятыми список инициализатора-статьи инициализирует членов
субагрегат; ошибочно быть более
инициализатора-статьи чем участники. Если, однако, инициализатора-лист
для субагрегата не начинается с левой скобки, то только
довольно инициализатора-статьи из списка взяты для инициализации
члены субагрегата; любой оставшийся инициализатора-статьи являются
осталось инициализировать следующий элемент совокупности которых
текущий субагрегат является членом.

Применяя это к первому std::array-пример:

static std::array<A, 4> x1 =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};

Это интерпретируется следующим образом:

static std::array<A, 4> x1 =
{        // x1 {
{      //   __arr {
1,   //     __arr[0]
2    //     __arr[1]
//     __arr[2] = {}
//     __arr[3] = {}
}      //   }

{3,4}, //   ??
{5,6}, //   ??
...
};       // }

Первый { берется как инициализатор std::array структура. инициализатора-статьи {1,2}, {3,4} и т. д. затем принимаются в качестве инициализаторов субагрегатов std::array, Обратите внимание, что std::array имеет только один субагрегат __arr, С первого инициализатор-раздел {1,2} начинается с {, исключение скобки не происходит, и компилятор пытается инициализировать вложенный A __arr[4] массив с {1,2}, Остальные инициализатора-статьи {3,4}, {5,6} и т. д. не относятся ни к одному из подгрупп std::array и поэтому являются незаконными.

В третьем и четвертом примере первый инициализатор-раздел для субагрегата std::array не начинается с {, поэтому применяется исключение скобки:

static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};

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

static std::array<A, 4> x4 =
{             // x4 {
//   __arr {       -- brace elided
A{ 1, 2 },  //     __arr[0]
{ 3, 4 },   //     __arr[1]
{ 5, 6 },   //     __arr[2]
{ 7, 8 }    //     __arr[3]
//   }             -- brace elided
};            // }

Следовательно A{1,2} вызывает все четыре инициализатора-статьи использоваться для инициализации вложенного массива в стиле C. Если вы добавите другой инициализатор:

static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
X
};

тогда это X будет использоваться для инициализации следующего субагрегата std::array, Например.

struct outer {
struct inner {
int a;
int b;
};

inner i;
int c;
};

outer o =
{        // o {
//   i {
1,     //     a
2,     //     b
//   }
3      //   c
};       // }

Brace-elision использует следующие N предложений инициализатора, где N определяется числом инициализаторов, необходимых для (суб) агрегата, который должен быть инициализирован. Следовательно, имеет значение только то, начинается ли первое из этих N-предложений инициализатора с {,

Больше похоже на ОП:

struct inner {
int a;
int b;
};

struct outer {
struct middle {
inner i;
};

middle m;
int c;
};

outer o =
{              // o {
//   m {
inner{1,2},  //     i
//   }
3            //   c
};             // }

Обратите внимание, что brace-elision применяется рекурсивно; мы можем даже написать запутанную

outer o =
{        // o {
//   m {
//     i {
1,     //       a
2,     //       b
//     }
//   }
3      //   c
};       // }

Где мы опускаем обе скобки для o.m а также o.m.i, Первые два предложения инициализатора используются для инициализации o.m.i, оставшийся инициализирует o.c, Как только мы вставим пару скобок вокруг 1,2, это интерпретируется как пара фигурных скобок, соответствующих o.m:

outer o =
{        // o {
{      //   m {
//     i {
1,   //       a
2,   //       b
//     }
}      //   }
3      //   c
};       // }

Здесь инициализатор для o.m действительно начинается с {следовательно, исключение скобок не применяется. Инициализатор для o.m.i является 1, который не начинается с {следовательно, скобка-исключение применяется для o.m.i и два инициализатора 1 а также 2 потребляются.

28

Другие решения


По вопросам рекламы [email protected]