Рассмотрим следующий код:
#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?.)
Мой вопрос: почему именно компилируется финальная инициализация?
Краткая версия: предложение инициализатора, которое начинается с {
останавливает скобку Это имеет место в первом примере с {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
потребляются.