Here are 8 ways to declare and initialize arrays in C++11 that seems ok under g++
:
/*0*/ std::array<int, 3> arr0({1, 2, 3});
/*1*/ std::array<int, 3> arr1({{1, 2, 3}});
/*2*/ std::array<int, 3> arr2{1, 2, 3};
/*3*/ std::array<int, 3> arr3{{1, 2, 3}};
/*4*/ std::array<int, 3> arr4 = {1, 2, 3};
/*5*/ std::array<int, 3> arr5 = {{1, 2, 3}};
/*6*/ std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
/*7*/ std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});
What are the correct ones according to the strict standard (and the upcoming C++14 standard) ? What are the most common/used and those to avoid (and for what reason) ?
C++11 summary / TL;DR
std::array
contains a raw array. Therefore, examples 1, 3, 5, 7 are not required to work. However, I do not know of a Standard Library implementation where they do not work (in practice).std::array<int, 3> arr4 = {1, 2, 3};
I'd prefer version 4 or version 2 (with the brace elision fix), since they initialize directly and are required/likely to work.
For Sutter's AAA style, you can use auto arrAAA = std::array<int, 3>{1, 2, 3};
, but this requires the brace elision fix.
std::array
is required to be an aggregate [array.overview]/2, this implies it has no user-provided constructors (i.e. only default, copy, move ctor).
std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});
An initialization with (..)
is direct-initialization. This requires a constructor call. In the case of arr0
and arr1
, only the copy/move constructor are viable. Therefore, those two examples mean create a temporary std::array
from the braced-init-list, and copy/move it to the destination. Through copy/move elision, the compiler is allowed to elide that copy/move operation, even if it has side effects.
N.B. even though the temporaries are prvalues, it might invoke a copy (semantically, before copy elision) as the move ctor of std::array
might not be implicitly declared, e.g. if it were deleted.
std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});
These are examples of copy-initialization. There are two temporaries created:
{1, 2, 3}
to call the copy/move constructorstd::array<int, 3>(..)
the latter temporary then is copied/moved to the named destination variable. The creation of both temporaries can be elided.
As far as I know, an implementation could write an (This possibility is ruled out by [container.requirements.general], kudos to David Krauss, see this discussion.)explicit array(array const&) = default;
constructor and not violate the Standard; this would make those examples ill-formed.
std::array<int, 3> arr2{1, 2, 3};
std::array<int, 3> arr3{{1, 2, 3}};
std::array<int, 3> arr4 = {1, 2, 3};
std::array<int, 3> arr5 = {{1, 2, 3}};
This is aggregate-initialization. They all "directly" initialize the std::array
, without calling a constructor of std::array
and without (semantically) creating a temporary array. The members of the std::array
are initialized via copy-initialization (see below).
On the topic of brace-elision:
In the C++11 Standard, brace elision only applies to declarations of the form T x = { a };
but not to T x { a };
. This is considered a defect and will be fixed in C++1y, however the proposed resolution is not part of the Standard (DRWP status, see top of the linked page) and therefore you cannot count on your compiler implementing it also for T x { a };
.
Therefore, std::array<int, 3> arr2{1, 2, 3};
(examples 0, 2, 6) are ill-formed, strictly speaking. As far as I know, recent versions of clang++ and g++ allow the brace elision in T x { a };
already.
In example 6, std::array<int, 3>({1, 2, 3})
uses copy-initialization: the initialization for argument passing is also copy-init. The defective restriction of brace elision however, "In a declaration of the form T x = { a };
", also disallows brace elision for argument passing, since it's not a declaration and certainly not of that form.
On the topic of aggregate-initialization:
As Johannes Schaub points out in a comment, it is only guaranteed that you can initialize a std::array
with the following syntax [array.overview]/2:
array<T, N> a = { initializer-list };
You can deduce from that, if brace-elision is allowed in the form T x { a };
, that the syntax
array<T, N> a { initializer-list };
is well-formed and has the same meaning. However, it is not guaranteed that std::array
actually contains a raw array as its only data member (also see LWG 2310). I think one example could be a partial specialization std::array<T, 2>
, where there are two data members T m0
and T m1
. Therefore, one cannot conclude that
array<T, N> a {{ initializer-list }};
is well-formed. This unfortunately leads to the situation that there's no guaranteed way of initializing a std::array
temporary w/o brace elision for T x { a };
, and also means that the odd examples (1, 3, 5, 7) are not required to work.
All of these ways to initialize a std::array
eventually lead to aggregate-initialization. It is defined as copy-initialization of the aggregate members. However, copy-initialization using a braced-init-list can still directly initialize an aggregate member. For example:
struct foo { foo(int); foo(foo const&)=delete; };
std::array<foo, 2> arr0 = {1, 2}; // error: deleted copy-ctor
std::array<foo, 2> arr1 = {{1}, {2}}; // error/ill-formed, cannot initialize a
// possible member array from {1}
// (and too many initializers)
std::array<foo, 2> arr2 = {{{1}, {2}}}; // not guaranteed to work
The first tries to initialize the array elements from the initializer-clauses 1
and 2
, respectively. This copy-initialization is equivalent to foo arr0_0 = 1;
which in turn is equivalent to foo arr0_0 = foo(1);
which is illegal (deleted copy-ctor).
The second does not contain a list of expressions, but a list of initializers, therefore it doesn't fulfil the requirements of [array.overview]/2. In practice, std::array
contains a raw array data member, which would be initialized (only) from the first initializer-clause {1}
, the second clause {2}
then is illegal.
The third has the opposite problem as the second: It works if there is an array data member, but that isn't guaranteed.