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

C++C++14Initializer List

C++ Problem Overview


With the C++14 standard, the initialization of an std::array can go with single braces (see http://en.cppreference.com/w/cpp/container/array):

This, however, does not work for an std::array of std::pair.

Why do these work:

std::pair<int, int> p { 1, 2 };
std::array<int, 3> a {1, 2, 3};

but does this not work:

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

while this does work again?

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

Also, for completion, the initialization of a good old array does work with single braces

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

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

The C++14 brace elision rule applies only to subaggregate initialization.

So for example something like this works:

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

Here an aggregate of aggregates can be list-initialized without extra braces.

But std::pair is not an aggregate (it has constructors), so the rule does not apply.

Which means that without the brace elision rule, std::array, itself being an aggregate with an array inside, needs an extra set of braces in order to be list-initialized. Remember that the class template array is implemented as:

template<typename T, std::size_t N> 
struct array {
  T elems[N];
};

To list-initialize it without the brace elision rule, you need an extra set of braces to get to the elems member.

Solution 3 - C++

Without the double braces, the statement is simply ambiguous. Consider the following code:

    std::array<std::pair<int, int>, 1> a = {{ {1, 2} }};
    std::array<int, 2> b = { {1, 2} };

Without double braces in the first definition, the compiler will treat { {1,2} } as a scalar initialization list for array<int, 2>. You need to declare an explicit nested braced-init-list in order for the compiler to recognize that the inner list is also aggregate-initialized (vs. scalar initialized), such that it can construct an array of std::pair.

Solution 4 - C++

In theory std::array should be initialized with aggregate initialization. So actually this:

std::array<int, 3> a {1, 2, 3};

is a syntactic sugar for this:

std::array<int, 3> a {{1, 2, 3}};

As You see, in the first one it seems I initialize array with values, but it is really aggregate initialization with braced init-list. This is clear as a day in the second situation. So that's for starters.

Ok so why does not this work?

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

Well, simply put - the compiler can't distinguish what type of syntax You are using to initialize the array. {1, 11} can be interpreted both as initializer list and use the first version or it can be interpreted as a pair and go with the second version.

This code:

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

removes ambiguity.

Source: http://en.cppreference.com/w/cpp/language/aggregate_initialization

Solution 5 - C++

I'm going to guess here.
The initializer list for std::array<T,n> should be a list of T (or trivially constructable to T). So you could do

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

but that is tediously verbose. In order to get the conversion to std::pair<int,int> you want, you need to provide an initializer list, so

std::array<std::pair<int,int>,3> b {
    { // element 1
      { // initialize from:
        { 1,11 } // std::initializer_list
       }
     },
  ...
};

I can't defend this further, but note that std::vector<T, Allocator>::vector( std::initializer_list<T>, const Allocator& alloc=Allocator()) is defined but std::array<T,n>::array( std::initializer_list<T> ) is not. Neither is std::pair<U,T>::pair( std::initializer_list<??> ) defined.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChielView Question on Stackoverflow
Solution 1 - C++JodocusView Answer on Stackoverflow
Solution 2 - C++rustyxView Answer on Stackoverflow
Solution 3 - C++andreeeView Answer on Stackoverflow
Solution 4 - C++bartopView Answer on Stackoverflow
Solution 5 - C++jwmView Answer on Stackoverflow