How to Omit the Double-Braces for Std::Array in C++14

Can we omit the double-braces for std::array in C++14?

Actually you can write the following in C++11 also:

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

It is completely valid syntax.

What is not allowed in C++11 though is something like this case (see that topic; I don't want to write this here again, it is a long post). So if you ask that then, yes, we can omit the extra braces in C++14. This is the proposal:

  • Uniform initialization for arrays and class aggregate types

  • The introduction says

    This document proposes a slight relaxation of the rules for eliding braces from aggregate initialization in order to make initialization of arrays and class aggregates more uniform. This change is required in order to support class aggregate types with a single member subaggregate that behave similarly to their array counterparts (i.e. std::array).

Hope that helps.

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.

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.

std::array aggregate initialization requires a confusing amount of curly braces

std::array is defined as a structure that contains an array.

Thus the first pair of braces are used to initialize data members of the structure that is the array.
The second pair of braces is used to initialize the array within the structure.
And the third pairs of braces are used to initialize each object of type std::pair.

To be more precise then according to the C++ Standard (23.3.2.1 Class template array overview)

2 An array is an aggregate (8.5.1) that can be initialized with the
syntax

array<T, N> a = { initializer-list }; 

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

Is it correct to initialize std::array by one pair of curly brackets if zeroed array needed?

According to the C++ Standard (8.5.1 Aggregates)

7 If there are fewer initializer-clauses in the list than there are
members in the aggregate, then each member not explicitly initialized
shall be initialized from its brace-or-equal-initializer or, if there
is no brace-or-equalinitializer, from an empty initializer list
(8.5.4).

and (8.5.4 List-initialization p.#3)

— Otherwise, if the initializer list has no elements, the object is
value-initialized.

Thus initializations

std::array<int, 10> array {};

and

std::array<int, 10> array { {  } };

are equivalent.

Nested aggregate initialization of std::array

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 have some questions about the way to assign values to the std::array

Double-braces required in C++11 prior to the CWG 1270 (not needed in C++11 after the revision and in C++14 and beyond):

// construction uses aggregate initialization
std::array<int, 5> a{ {1, 2, 3, 4, 5} }; // double-braces required in C++11 prior to the CWG 1270 revision
std::array<int, 5> a{1, 2, 3, 4, 5}; // not needed in C++11 after the revision and in C++14 and beyond
std::array<int, 5> a = {1, 2, 3, 4, 5}; // never required after =

std::array reference



Related Topics



Leave a reply



Submit