Ответ 1
Краткая версия: Параметр initializer, начинающийся с {
, останавливает скобки. Это имеет место в первом примере с {1,2}
, но не в третьем и четвертом, которые используют A{1,2}
. Элемент brace-elision потребляет следующие N аргументов инициализатора (где N зависит от инициализации агрегата), поэтому только первое предложение инициализатора N не должно начинаться с {
.
Во всех реализациях стандартной библиотеки С++, о которой я знаю, std::array
- это структура, содержащая массив C-style. То есть, у вас есть совокупность, которая содержит суб-агрегат, очень похожий на
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-стиля.
С++ позволяет в агрегатной инициализации опустить некоторые фигурные скобки при инициализации вложенных агрегатов. Например:
struct outer {
struct inner {
int i;
};
inner x;
};
outer e = { { 42 } }; // explicit braces
outer o = { 42 }; // with brace-elision
Правила заключаются в следующем (с использованием проекта post-N4527, который является пост-С++ 14, но С++ 11 содержит дефект, связанный с этим в любом случае):
Брекеты можно отбросить в списке инициализаторов следующим образом. Если Список инициализации начинается с левой скобки, затем следующая список инициализаторов-разделов, разделенных запятыми, инициализирует члены субагрегат; ошибочно, чтобы было больше initializer-clauses, чем члены. Если, однако, список инициализаторов для суммирования не начинается с левой скобки, тогда только достаточно инициализатор-предложения из списка, чтобы инициализировать члены подгруппы; любые оставшиеся условия инициализации слева, чтобы инициализировать следующий член совокупности, из которого текущий субагрегат является членом.
Применив это к первому 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
имеет только один элемент subggregate __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-инициализаторов-предложений начинается с {
.
Аналогично OP:
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
}; // }
Обратите внимание, что привязка фигуры применяется рекурсивно; мы можем даже написать запутанную
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
применяется скобка-elision, и используются два инициализатора 1
и 2
.