Why Can't Simple Initialize (With Braces) 2D Std::Array

Why can't simple initialize (with braces) 2D std::array?

std::array<T, N> is an aggregate that contains a C array. To initialize it, you need outer braces for the class itself and inner braces for the C array:

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

Applying this logic to a 2D array gives this:

std::array<std::array<int, 3>, 2> a2 { { { {1, 2, 3} }, { { 4, 5, 6} } } };
// ^ ^ ^ ^ ^ ^
// | | | | | |
// | +-|-+------------|-+
// +-|-+-|------------+---- C++ class braces
// | |
// +---+--- member C array braces

Why can't a 2D std::array be initialized with two layers of list-initializers?

The container std::array is equivalently a struct holding a C-array (an implementation may not implement std::array in this way, but it should guarantee the semantic is the same), so it should be initialized by two layers of braces, i.e.

#include <array>
std::array<std::array<double,2>,2> f() {
return {{{{0,0}},{{0,0}}}};
}

Of course, braces in an initializer-list can be elided like what we usually do for a 2D array:

int arr[2][2] = {0,1,2,3};

... but the initializer-list that begins with the elided braces before the elision should not begin with a left brace after the elision. In other words, if an initializer-list begins with a left brace, the compiler will not consider the possibility that this initializer-list has elided outermost braces.

In your initializer {{0,0},{0,0}}, the sub-initializer {0,0},{0,0} begins with a left brace, so it is used to initialize the C-array itself. However, there are two clauses in the list while there is only one C-array, an error occurs.

In your initializer {std::array<double,2>{0,0},{0,0}}, the sub-initializer std::array<double,2>{0,0},{0,0} does not begin with a left brace, so it can be used to initialize the elements of the C-array, which is OK (recursively, {0,0} is OK to initialize an std::array<double,2> because the sub-initializer 0,0 does not begin with a left brace).


A suggestion: with this elision rule of braces, you can elide all inner braces, just like what we usually do for a 2D array:

#include <array>
std::array<std::array<double,2>,2> f() {
return {0,0,0,0};
}

List initialisation of two dimensional std::array

In this declaration

std::array<std::array<int,2>,2> x = {{0,1},{2,3}};

you have three nested aggregates. The first pair of values enclosed in braces

{0,1}

is considered by the compiler as an initializer of the second aggregate that is present in the declaration as one sub-aggregate. So the second pair of values in braces

{2,3}

are considered by the compiler as redundant that has no corresponding object.

You could declare the array for example like

std::array<std::array<int, 2>, 2> x = { { {0,1},{2,3} } };

The braces may be elided when an aggregate is initialized. (C++17 Standard, 11.6.1 Aggregates)

12 Braces can be elided in an initializer-list as follows. If the
initializer-list begins with a left brace, then the succeeding
comma-separated list of initializer-clauses initializes the elements
of a subaggregate; it is erroneous for there to be more
initializer-clauses than elements. If, however, the initializer-list
for a subaggregate does not begin with a left brace, then only enough
initializer-clauses from the list are taken to initialize the elements
of the subaggregate; any remaining initializer-clauses are left to
initialize the next element of the aggregate of which the current
subaggregate is an element.

So in this declaration

std::array<std::array<int,2>,2> x = {0,1,2,3};

the braces are elided and the aggregate is initialized as it is described in the quote..

In this declaration

std::vector<std::vector<int>> y = {{0,1},{2,3}};

there is used the constructor of the class std::vector that accepts std::initializer_list as an argument. In this case the constructor builds as many elements of the vector as there are elements in the initializer list.

how do I declare a 2d std::array

std::array is 1-dimensional, there is no such thing as a 2-dimensional std::array. You would simply have to use an inner std::array as the element type of an outer std::array, eg:

#include <iostream>
#include <array>

int main(){
std::array<std::array<int,5>,4> myarray;
for (int i=0; i<5; i++){
for (int j=0; j<10; j++){
myarray[i].at(j) = j+1;
}
}
}

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 can't std::array<std::pair<int,int>, 3> be initialized using nested initializer lists, but std::vector<std::pair<int,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 is the C++ initializer_list behavior for std::vector and std::array different?

std::array<T, N> is an aggregate: it doesn't have any user-declared constructors, not even one taking a std::initializer_list. Initialization using braces is performed using aggregate initialization, a feature of C++ that was inherited from C.

The "old style" of aggregate initialization uses the =:

std::array<int, 4> y = { { 1, 2, 3, 4 } };

With this old style of aggregate initialization, extra braces may be elided, so this is equivalent to:

std::array<int, 4> y = { 1, 2, 3, 4 };

However, these extra braces may only be elided "in a declaration of the form T x = { a };" (C++11 §8.5.1/11), that is, when the old style = is used . This rule allowing brace elision does not apply for direct list initialization. A footnote here reads: "Braces cannot be elided in other uses of list-initialization."

There is a defect report concerning this restriction: CWG defect #1270. If the proposed resolution is adopted, brace elision will be allowed for other forms of list initialization, and the following will be well-formed:

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

(Hat tip to Ville Voutilainen for finding the defect report.)

Missing braces for multi-dimensional arrays

What you have is called aggregate initialization via brace elision, you are perfectly fine, the code is standard compliant.

From cppreference.com:

If the aggregate initialization uses the form with the equal sign (T a
= {args..}), (until C++14) the braces around the nested initializer lists may be elided (omitted), in which case as many initializer
clauses as necessary are used to initialize every member or element of
the corresponding subaggregate, and the subsequent initializer clauses
are used to initialize the following members of the object. However,
if the object has a sub-aggregate without any members (an empty
struct, or a struct holding only static members), brace elision is not
allowed, and an empty nested list {} must be used.

See more details here and here.



Related Topics



Leave a reply



Submit