Initializing std::array

J

Juha Nieminen

How to properly initialize a std::array<std::array<float, 2>, 2> using
an initializer list?
 
C

Casey Carter

How to properly initialize a std::array<std::array<float, 2>, 2> using
an initializer list?

According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
that can be initialized with the syntax

array<T, N> a = { initializer-list };

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

In practice, implementations of array<T, N> contain a single data
member: a C-style array of N Ts. The initialization specified above only
works because of rules allowing for elision of some curly braces in
initializers. Some compilers will warn about missing braces.

The fully-braced form of initializer would then be

array<T, N> a = {{ initializer-list }};

Which, despite being non-standard, is in fact _more_ correct for all
reasonable implementations of std::array. It will also work correctly in
the presence of nesting:

array<array<float, 2>, 2> foo = {{ {{ 1.0, 2.0 }}, {{ 3.0, 4.0 }} }};
 
J

Juha Nieminen

Casey Carter said:
According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
that can be initialized with the syntax

array<T, N> a = { initializer-list };

If that's the case, then neither gcc nor clang implement it properly
because both give a warnings when I try it, rather than just working
as intended.
The fully-braced form of initializer would then be

array<T, N> a = {{ initializer-list }};

Which, despite being non-standard

So what would be the standard way?
 
C

Casey Carter

If that's the case, then neither gcc nor clang implement it properly
because both give a warnings when I try it, rather than just working
as intended.


So what would be the standard way?

The single-braced version is blessed by the standard, presumably because
it looks like

T a[N] = { initializer-list };

and makes it easy to directly replace "T a[N]" with "array<T, N> a" in
typical code. GCC and clang both implement it correctly, they just
happen to also emit a very annoying diagnostic.

Considering how that initialization style depends on dropping braces,
there's no way it can possibly work in the presence of nested arrays.
 
J

Juha Nieminen

Casey Carter said:
Considering how that initialization style depends on dropping braces,
there's no way it can possibly work in the presence of nested arrays.

I think that's the most relevant point relating to my original question.
If I understand correctly, this should work according to the C++ standard:

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

However, both gcc and clang give an outright error.

Now I wonder why neither has implemented it properly. Initializing an
array from an initializer list is quite a trivial thing to do, requiring
just a few lines of code.

(Although there's one problem with that: std::initializer_list::size()
is defined as const, not constexpr. This ought to mean that it's not
possible to detect at compile time if there are too many elements in
the initializer list, eg. with a static_assert... I suppose the compilers
would have to do some trickery about that.)
 
N

Nobody

If that's the case, then neither gcc nor clang implement it properly
because both give a warnings when I try it,

The implementation is allowed to emit warnings about anything it likes,
including perfectly valid code.

The standard only requires the implementation to accept the above syntax;
it doesn't require it to do so quietly.
 
J

Juha Nieminen

Nobody said:
The implementation is allowed to emit warnings about anything it likes,
including perfectly valid code.

The standard only requires the implementation to accept the above syntax;
it doesn't require it to do so quietly.

But as noted, they do not accept things like

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

even though they should.
 
N

Nobody

But as noted, they do not accept things like

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

even though they should.

You can't omit that particular set of braces.

A std::array<T,N> is typically a structure containing a single data
member, an N-element array. On that basis, the initialiser list for a
std::array<T,N> should have the same structure as the initialiser list as
a 1xN C array, i.e.:

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

One set of brackets can be omitted, allowing:

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

In the 2D case, the initialiser list for a std::array<std::array<T,N>,M>
should have the same structure as that for a 1xMx1xN C array, i.e.:

// compiles without warning
std::array<std::array<int, 2>, 2> c = { { { { 1, 2 } }, { { 3, 4 } } } };

You can use either of:

// compiles, with warnings
std::array<std::array<int, 2>, 2> d = { { { 1, 2 }, { 3, 4 } } };
std::array<std::array<int, 2>, 2> e = { 1, 2, 3, 4 };

but not:

// does not compile
std::array<std::array<int, 2>, 2> g = { { 1, 2 }, { 3, 4 } };

because that would try to use {1,2} to initialise the first (and only)
data member of the outer array then {3,4} to initialise the second data
member, which doesn't exist.

However, while this implementation is typical, it isn't required. AFAICT,
the standard only requires that the implementation is such that a
one-dimensional array of scalars can be initialised like a C array. E.g.
the implementation could actually have the underlying array nested within
multiple levels of single-element structures.

The rules on brace elision mean that the 1D case can be initialised like a
1D C array, but the initialiser list for a nested array requires braces
which match the implementation.

It isn't possible to specify the syntax for higher dimensions without
either specifying the implementation or changing the base language syntax.
 
J

Juha Nieminen

Nobody said:
It isn't possible to specify the syntax for higher dimensions without
either specifying the implementation or changing the base language syntax.

But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
fine. What's so different about std::array?
 
N

Nobody

But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
fine.

Only for C++11. Previously, you couldn't use any form of initialiser list.
What's so different about std::array?

std::array doesn't have a constructor, so it can only be initialised by
aggregate initialisation.
 
C

Casey Carter

But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
fine. What's so different about std::array?

The initialization semantics of std::array are exactly as specified in
(23.3.2.1/2):

An array is an aggregate (8.5.1) that can be initialized with the syntax

array<T, N> a = { initializer-list };

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

That doesn't mean std::initializer_list and it's not even the same word
"initializer-list" as used elsewhere in the standard, since they define
it here as "a comma-separated list of up to N elements whose types are
convertible to T." I don't think that "{1, 2}" in your expression _has_
a type (at best it would be std::initializer_list<int>) but it certainly
doesn't have a type convertible to T.

AFAICT the only compliant and portable way to initialize nested arrays
is with e.g.:

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

which will give a small raft of compiler warnings.

If this all seems ridiculous to you, you are not alone. The standard
does some contortions to satisfy the requirement that std::array be an
aggregate, so that std::array can fit nearly all use cases for C-style
arrays.
 
J

Juha Nieminen

Casey Carter said:
If this all seems ridiculous to you, you are not alone. The standard
does some contortions to satisfy the requirement that std::array be an
aggregate, so that std::array can fit nearly all use cases for C-style
arrays.

"Nearly" meaning "with the exception of multidimensional arrays, which
cannot be initialized in the same way as multidimensional C-style arrays"?

I'm still not quite sure what would be the problem in std::array supporting
std::initializer_list, except perhaps for the small annoyance that there
doesn't seem to be a way to get the size of the initializer list at
compile time in order to give an error if too many elements are specified.

(Since std::initializer_list is not, AFAIK, a dynamic data container, is
there a technical reason why its size() function cannot be constexpr?)
 
P

ptyxs

Le 13/09/2012 20:27, Casey Carter a écrit :
An array is an aggregate (8.5.1) that can be initialized with the syntax

array<T, N> a = { initializer-list };

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

Note that possible initialization examples are :

array<int,3> { 3,45,76 }; // (no '=' signe, probably better style

and :

array<int,3> { { 3,45,76 } };
 
Ö

Öö Tiib

(Since std::initializer_list is not, AFAIK, a dynamic data container, is
there a technical reason why its size() function cannot be constexpr?)

Probably one of the big players screwed it in to make that as "extension"
for their compiler and achieve that std::initializer_list's usage and
provided guarantees (that enable optimizations) would differ per
platform.
 
C

Casey Carter

"Nearly" meaning "with the exception of multidimensional arrays, which
cannot be initialized in the same way as multidimensional C-style arrays"?

The other big incompatibility is arrays whose size is determined by the
number of initializers:

int foo[] = { 1, 2, 3, 4, 5 };

for which std::array has no equivalent.
I'm still not quite sure what would be the problem in std::array supporting
std::initializer_list, except perhaps for the small annoyance that there
doesn't seem to be a way to get the size of the initializer list at
compile time in order to give an error if too many elements are specified.

Being an aggregate per 8.5.1 implies no constructors, which rules out
construction from std::initializer_list.
(Since std::initializer_list is not, AFAIK, a dynamic data container, is
there a technical reason why its size() function cannot be constexpr?)

No problem at all; the standard library that comes with GCC 4.7
implements all of std::initializer_list as constexpr. There are many
things in the library that could be constexpr but are not required to
be, I imagine because constexpr was devised somewhat late in the
standardization process and didn't have time to percolate through all of
the library requirements.

What _is_ problematic is writing a constructor that takes a
std::initializer_list for a std::array equivalent, while accounting for:
(a) the initializer_list might not have exactly N elements
(b) T might not be default constructable
(c) T might not be copy constructable
(d) T might have a throwing copy constructor
The only way to construct a C-style array - i.e., without requiring
default initialization and/or copying - is with a good-old fashioned
braced aggregate initializer.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top