How to Brace-Initialize an Std::Array of Std::Pairs

Why can't std::arraystd::pairint,int, 3 be initialized using nested initializer lists, but std::vectorstd::pairint,int can?

You need to add an outer pair of braces to initialize the std::array<...> object itself:

std::array <std::pair<int,int>, 3> a{{{1,2},{3,4},{5,6}}};

The outermost pair is for the array object, the second pair is for the aggregate array inside the object. Then the list of elements in the array.

Why does initialization of array of pairs still need double braces in C++14?

This appears to be a parsing ambuguity somewhat similar to the famous most vexing parse. I suspect what's going on is that:

If you write

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

the compiler has two ways to interpret the syntax:

  1. You perform a full-brace initialization (meaning the outermost brace refers to the aggregate initialization of the std::array, while the first innermost one initializes the internal member representation of std::array which is a real C-Array). This will fail to compile, as std::pair<int, int> subsequently cannot be initialized by 1 (all braces are used up). clang will give a compiler error indicating exactly that:

    error: no viable conversion from 'int' to 'std::pair<int, int>'
    std::array<std::pair<int, int>, 3> a{{1, 11}, {2, 22}, {3, 33}};
    ^

    Note also this problem is resolved if there is no internal member aggregate to be initialized, i.e.

    std::pair<int, int> b[3] = {{1, 11}, {2, 22}, {3, 33}};

    will compile just fine as aggregate initialization.

  2. (The way you meant it.) You perform a brace-elided initialization, the innermost braces therefore are for aggregate-initialization of the individual pairs, while the braces for the internal array representations are elided. Note that even if there wasn't this ambiguity, as correctly pointed out in rustyx's answer, the rules of brace elision do not apply as std::pair is no aggregate type so the program would still be ill-formed.

The compiler will prefer option 1. By providing the extra braces, you perform the full-brace initialization and lift any syntactical ambiguity.

Why do I need the extra pair of curly braces when defining an array of pairs?

std::array is a wrapper template around built-in C-style array. You may think of it as something like

template <typename T, std::size_t N>
class array {
T arr[N];
...
};

Both std::array and built-in C-style array are aggregate types. You initialize it using aggregate initialization syntax. It would be something like

std::array<T, N> array = {{ t0, t1, t2 }};

The outer {} is for std::array itself while the inner {} is for the wrapped built-in C-style array.

Look at your example

std::array<std::pair<sf::IntRect, sf::IntRect>, 1> aliens{
{sf::IntRect{2,3,1,2}, sf::IntRect{4,1,3,2}}
};

The error is more obvious when reformated as below.

std::array<std::pair<sf::IntRect, sf::IntRect>, 1> aliens{{
sf::IntRect{2,3,1,2},
sf::IntRect{4,1,3,2}
}};

You are trying to initialize a pair with a single sf::IntRect object.

Double braces required for list-initialization of container std::array

Braces, nested however deeply, are matched against the structure of the object being initialized before considering any type information. Since std::array<T,N> must contain a true T[N] (rather than be one), the structure is that there is one object inside the array—that is, the array. Two opening braces are therefore taken to begin the initializer for that array, which doesn’t work if the entire nested set is needed to initialize one array element, nor if there is more than one such nested set.

When some initializer clause is an expression, even A{…}, this decomposition stops and the initializer is used for one subobject. However, if that expression cannot be converted to the type of the appropriate subobject and that type is itself an aggregate, brace elision takes place and the subsequent initializers are used for its subobjects despite the lack of braces. When that applies to the T[N] object itself, the array may be initialized with only one layer of braces.

So, in short, an open brace causes decomposition of an aggregate, whether it works or not, but a type mismatch also causes decomposition of an aggregate.

Initializing std::pair via std::initializer_list doesn't work while initializing an std::array?

Initializing std::array with a braced initializer list is a bit tricky, because you need an extra set of braces (as it is an aggregate):

array<pair<char,char>, 3> p = {{ {'{','}'}, {'[',']'}, {'(',')'} }};
^ ^

std::vector is different, because using a braced initializer list will result in the std::initializer_list constructor being called, and not using aggregate initialization like std::array.

Aggregate initialization for array of std::pair

Aggregate-style initialization of class types ("uniform initialization") has been legal for the last 5 years, since C++11 was released.

Older versions of gcc default to the ancient C++03 standard (or the even older C++98), but are aware of C++11 so will under some circumstances apply C++11 rules where this is relatively unambiguous. This is the meaning of the warning:

warning: extended initializer lists only available with -std=c++11 or -std=gnu++11

Some even older versions of gcc may be applying their own (pre-C++11) extensions and so give different warnings.

You should compile in C++11 mode (at least), or use a modern compiler that defaults to C++11 or C++14, such as gcc 6.1.



Related Topics



Leave a reply



Submit