This question already has an answer here:
-
When can outer braces be omitted in an initializer list?
1 answer
I wonder, why declaration of std_arr
in the following code generates an error, while c_arr
compiles well:
struct S { int a, b; };
S c_arr[] = {{1, 2}, {3, 4}}; // OK
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // Error: too many initializers
Both std::array
and S
are aggregates. From aggregate initialization on cppreference.com:
If the initializer clause is a nested braced-init-list (which is not an expression and has no type), the corresponding class member is
itself an aggregate: aggregate initialization is recursive.
Why this initialization of std::array
does not compile?
The braces in aggregate initialisation are largely optional, so you can write:
S c_arr[] = {1, 2, 3, 4}; // OK
std::array<S, 2> std_arr = {1, 2, 3, 4}; // OK
If you do add braces, though, then the braces are taken to apply to the next sub-object. Unfortunately, when you start nesting, this leads to silly code being valid, and sensible code like yours being invalid.
std::array<S, 2> std_arr = {{1, 2, 3, 4}}; // OK
std::array<S, 2> std_arr = {1, 2, {3, 4}}; // OK
std::array<S, 2> std_arr = {1, {2}, {3, 4}}; // OK
These are all okay. {1, 2, 3, 4}
is a valid initialiser for the S[2]
member of std_arr
. {2}
is okay because it is an attempt to initialise an int
, and {2}
is a valid initialiser for that. {3, 4}
is taken as an initialiser for S
, and it's also valid for that.
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // error
This is not okay because {1, 2}
is taken as a valid initialiser for the S[2]
member. The remaining int
sub-objects are initialised to zero.
You then have {3, 4}
, but there are no more members to initialise.
As pointed out in the comments,
std::array<S, 2> std_arr = {{{1, 2}, {3, 4}}};
also works. The nested {{1, 2}, {3, 4}}
is an initialiser for the S[2]
member. The {1, 2}
is an initialiser for the first S
element. The {3, 4}
is an initialiser for the second S
element.
I'm assuming here that std::array<S, 2>
contains an array member of type S[2]
, which it does on current implementations, and which I believe is likely to become guaranteed, but which has been covered on SO before and is not currently guaranteed.
Since the question is tagged C++14, I'll be quoting N4140. In [array] it says that std::array
is an aggregate:
2 An array
is an aggregate (8.5.1) that can be initialized with the
syntax
array a = { initializer-list };
where initializer-list is a comma-separated list of up to N
elements
whose types are convertible to T
.
In general, it's agreed that you need an extra pair of outer braces to initialize the underlying aggregate, which looks something like T elems[N]
. In paragraph 3, it's explained that this is for exposition purposes and not actually part of the interface. In practice, however, libstdc++ and Clang implement it like that:
template<typename _Tp, std::size_t _Nm>
struct __array_traits
{
typedef _Tp _Type[_Nm];
static constexpr _Tp&
_S_ref(const _Type& __t, std::size_t __n) noexcept
{ return const_cast<_Tp&>(__t[__n]); }
};
template<typename _Tp, std::size_t _Nm>
struct array
{
/* Snip */
// Support for zero-sized arrays mandatory.
typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;
typename _AT_Type::_Type _M_elems;
Clang:
template <class _Tp, size_t _Size>
struct _LIBCPP_TYPE_VIS_ONLY array
{
/* Snip */
value_type __elems_[_Size > 0 ? _Size : 1];
There are changes between C++11 and C++14 regarding aggregate initialization, however none that would make:
std::array<S, 2> std_arr = {{1, 2}, {3, 4}};
not ill-formed.