9

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?

Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • 4
    It should be `std::array std_arr{{ {1, 2}, {3, 4} }};` - outer ones surrounding constructor arguments, next pair for the initialiser list, inner pairs for each `S` element. C++14 will make it work with one less set of outer `{ }`. (The `=` is optional.) – Tony Delroy Feb 16 '15 at 12:35
  • 1
    @remyabel The rules of aggregate initialisation changed between C++11 and C++14, IIRC, and it's not clear to me that they aren't relevant here. –  Feb 16 '15 at 12:38
  • @hvd No changes regarding brace elision, though. – T.C. Feb 16 '15 at 12:42
  • You say : "hey I read the std and it says I should be able to do X, but when i do my compiler cries". Don't you think it might be relevant to specify the compiler and the version you are using ? – Félix Cantournet Feb 16 '15 at 12:42
  • @T.C. I wasn't sure about that, but I'll take your word for that. –  Feb 16 '15 at 12:48
  • @hvd According to cppreference, the C++14 change has to do with brace elision with a brace-or-equal initializer. However, I can't get it to error in C++11 mode in GCC or Clang, but otherwise, I think the answer to OP's question should be the same for both C++11 or C++14. –  Feb 16 '15 at 12:51
  • 2
    @remyabel Ah, and the answer to that almost-duplicate gives the answer: brace elision didn't change between C++11 and C++14, but it did change in a post-C++11 DRs that should be taken to apply to C++11. So it's correct that you don't see a difference in compilers with `-std=c++11` and `-std=c++14`: that DR resolution is applied even in C++11 mode. Aside from that, it covers `std::array std_arr {1, 2, 3, 4};` without the `=`, so it's not relevant to this particular question anyway. :) –  Feb 16 '15 at 12:57

2 Answers2

9

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.

  • I see it now. Logical, but very non-obvious. Thanks. – Mikhail Feb 16 '15 at 13:57
  • If you want to omit initializers for `b`; e.g. `std::array g = { {1}, {3} };` hoping to get `g[1].a == 3` ... then it seems all of the weird suggestions are unusable and double-bracing is the only option. So much for C++14 supposedly not requiring double-bracing for `std::array` ... – M.M May 15 '16 at 22:37
  • Once more an example how uniform initialisation is flawed... I personally would clearly prefer classic constructor syntax `std::array std_arr({{1, 2}, {3, 4}});` with parentheses... – Aconcagua Mar 21 '23 at 10:55
4

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.