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.
I believe they are all strictly conforming, except possibly arr2
. I would go with the arr3
way, because it is concise, clear, and definitely valid. If arr2
is valid (I'm just not sure), that would be even better, actually.
Combining parens and braces (0 and 1) never sits well with me, the equals (4 and 5) is ok, but I just prefer the shorter version, and 6 and 7 are just absurdly verbose.
However, you might want to go with yet another way, following Herb Sutter's "almost always auto" style:
auto arr8 = std::array<int, 3>{{1, 2, 3}};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With