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
Why Not Infer Template Parameter from Constructor
Is This Behavior of Vector::Resize(Size_Type N) Under C++11 and Boost.Container Correct
Py_Initialize Fails - Unable to Load the File System Codec
Accessing Inherited Variable from Templated Parent Class
Examples of When a Bitwise Swap() Is a Bad Idea
Why Does C++ Compilation Take So Long
How to Efficiently Select a Standard Library Container in C++11
Random Number Generation in C++11: How to Generate, How Does It Work
How to Loop Through a C++ Map of Maps
Why Is Conversion from String Constant to 'Char*' Valid in C But Invalid in C++
Correct Way of Declaring Pointer Variables in C/C++
What Do 1.#Inf00, -1.#Ind00 and -1.#Ind Mean
What Is Special About Numbers Starting With Zero
Print Leading Zeros With C++ Output Operator
Setting the Internal Buffer Used by a Standard Stream (Pubsetbuf)