0

Consider the following C++ code:

struct My_Struct
{
  int a;
  int b;
};

Now I want to declare a constant std::array of these structures:

Option A:

const std::array<My_Struct,2> my_array =
{
  {1,2},
  {2,3}
};

Option B:

const std::array<My_Struct,2> my_array =
{
  My_Struct(1,2),
  My_Struct(2,3)
};

Question: why does option A fail to compile and only option B compiles? What's wrong with nested curly braces when initializing std::array?

It seems to me that compiler should have all necessary information to successfully compile option A, yet it produces "too many initializer values" error.

Meanwhile, nested curly braces work really good when initializing plain arrays:

const My_Struct my_array[] =
{
      {1,2},
      {2,3}
};

Compiler is MSVC 17.2, C++20 mode on.

I can live with option B, but I would like to learn what exactly am I failing to understand in option A in terms of C++ knowledge.

Regus Pregus
  • 560
  • 3
  • 12

3 Answers3

9

std::array is a class that contains an actual array. It looks something like this:

template <typename T, size_t N>
struct array
{
    T _unspecified_name[N];

    // Member functions.
};

Note that std::array has no constructors or private data members, so it is an aggregate.

When you brace-initialize a std::array you're doing aggregate initialization of a class with one member that is itself an aggregate. Normally that would require two sets of braces:

//                       Braces for std::array
//                       │        │
//                       ▼        ▼
std::array<int, 2> arr = { {1, 2} };
//                         ▲    ▲
//                         │    │
//                         Braces for int[2] member

C++ has brace-elision rules to make this situation nicer though. You can omit the inner set of braces, and the initializers will "pass through" to underlying member objects that are themselves aggregates. In this case that's the std::array's int[2] member.

This is the root of your issue. When you initialize an array of My_Struct using braces, the compiler thinks you intended the inner set of braces to initialize the std::array's My_Struct[] member rather than the My_Struct within. Since there are two initializers when it was expecting only one it throws an error:

//                                  Initializer for std::array
//                                  │                │
//                                  ▼                ▼
std::array<My_Struct, 2> my_array = { {1, 2}, {2, 3} };
//                                    ▲    ▲  ▲    ▲
//                                    │    │  │    │
//      Initializer for unnamed My_Struct[2]  │    │
//                                            What is this initializer for? I only have one member to initialize

To avoid this situation you just need to add another set of braces:

//                                  ┌ Initializer for std::array ┐
//                                  │                            │
//                                  ▼                            ▼
std::array<My_Struct, 2> my_array = { {     {1, 2}, {2, 3}     } };
//                                    ▲     ▲    ▲  ▲    ▲     ▲
//                                    |     │    │  │    │     │
//                                    |     |Initializers│     │
//                                    |   For array elements   │
//                                    |                        |
//                               Initializer for unnamed My_Struct[2]
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
3

The problem with option A is that you're missing a set of braces {} corresponding to the inner C style array T[N].

From std::array's documentation

This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member.

This in turn means that, we will need outer braces for the class type std::array itself and then inner braces for the C style T[N] array.


Now lets apply this to your example and write the correct syntax. In your example T[N] is the same as My_Struct[2].

//-----------------------------------------v--------------v-------------> needed for inner C style array T[N] which is the same as My_Struct[2]
const std::array<My_Struct,2> my_array = { { {1,2}, {2,3} } };
//-------------------------------------------^---^--^---^---------------> needed for the object type `My_Struct`
//---------------------------------------^------------------^-----------> needed for class type std::array itself 
Jason
  • 36,170
  • 5
  • 26
  • 60
1

std:array is a container containing a C style array of My_Struct. So you need an extra curly bracket:

const std::array<My_Struct,2> my_array = {
  {
    {1,2},
    {2,3},
  }
};
Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42