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).An initialization with
(..)
is direct-initialization. This requires a constructor call. In the case ofarr0
andarr1
, only the copy/move constructor are viable. Therefore, those two examples mean create a temporarystd::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.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.This is aggregate-initialization. They all "directly" initialize the
std::array
, without calling a constructor ofstd::array
and without (semantically) creating a temporary array. The members of thestd::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 toT 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 forT 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 inT 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 formT 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:You can deduce from that, if brace-elision is allowed in the form
T x { a };
, that the syntaxis 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 specializationstd::array<T, 2>
, where there are two data membersT m0
andT m1
. Therefore, one cannot conclude thatis well-formed. This unfortunately leads to the situation that there's no guaranteed way of initializing a
std::array
temporary w/o brace elision forT 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:The first tries to initialize the array elements from the initializer-clauses
1
and2
, respectively. This copy-initialization is equivalent tofoo arr0_0 = 1;
which in turn is equivalent tofoo 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.