Инициализация std:: array
рассмотрим следующий код:
#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 ответов
Short version: предложение инициализатора, которое начинается с {
останавливает brace-elision. Это имеет место в первом примере с {1,2}
, но не в третьем ни четвертом, которые используют A{1,2}
. Brace-elision потребляет следующие N предложений инициализатора (где N зависит от инициализируемой совокупности), поэтому только первое предложение инициализатора N не должно начинаться с {
.
во всех реализациях стандартной библиотеки C++ я знаю, std::array
является структурой, которая содержит массив C-style. То есть, у вас есть совокупность, которая содержит суб-совокупность, как
template<typename T, std::size_t N>
struct array
{
T __arr[N]; // don't access this directly!
};
при инициализации std::array
С braced-init-list, поэтому вам придется инициализировать членов содержит массив. Поэтому в этих реализациях явная форма:
std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }};
самый внешний набор фигурных скобок относится к std::array
структура; второй набор фигурных скобок относится к вложенному массиву C-стиля.
C++ позволяет в инициализации aggregate опустить определенные фигурные скобки при инициализации вложенных агрегатов. Например:
struct outer {
struct inner {
int i;
};
inner x;
};
outer e = { { 42 } }; // explicit braces
outer o = { 42 }; // with brace-elision
правила следующие (используя проект post-N4527, который является post-C++14, но C++11 содержал дефект, связанный с этим в любом случае):
фигурные скобки можно снять в инициализатор-список следующим образом. Если инициализатор-список начинается с левой скобки, затем следует разделенный запятыми список инициализатор-п. инициализирует члены в subaggregate; это ошибочное больше инициализатор-п. чем члены. Если, однако,инициализатор-список для субагрегата не начинается с левой скобки, то только достаточно инициализатор-п. из списка берутся для инициализации члены subaggregate; оставшиеся инициализатор-п. несколько слева для инициализации следующего члена агрегата, который текущая subaggregate является членом.
применяя это к первому 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
структура. The инициализатор-п. {1,2}, {3,4}
etc. затем принимаются в качестве инициализаторов subaggregates из std::array
. Обратите внимание, что std::array
только один subaggregate __arr
. С первого инициализатор-п. {1,2}
начинается с {
, the скобка-исключение elision не происходит, и компилятор пытается инициализировать вложенные A __arr[4]
массив {1,2}
. Остальные инициализатор-п. {3,4}, {5,6}
etc. не относиться к любому subaggregate из std::array
и поэтому незаконны.
в третьем и четвертом примере первый инициализатор-п. для subaggregate из std::array
не начинается с {
, поэтому применяется исключение elision скобки:
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-style. Если вы добавите другой инициализатор:
static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
X
};
после этого X
будет использоваться для инициализировать следующим subaggregate из 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 определяется количеством инициализаторов, необходимых для инициализации (sub)агрегата. Поэтому имеет значение только то, начинается ли первое из этих 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
}; // }
обратите внимание, что скобка-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
начинается с {
, следовательно, скобка-elision не применяется. Инициализатор для o.m.i
это 1
, который не начинается с {
, следовательно расчалка-elision применяется для o.m.i
и два инициализатора 1
и 2
потребляются.