Brace elision in std::array<std::vector> - c++

I'm compiling using g++ for C++ 17. I have the following:
std::array<std::vector<int>, 2> v = {{ {1,2}, {3,4} }};
I don't understand why if I remove the double braces for the array it does not work anymore.
std::array<std::vector<int>, 2> v = { {1,2}, {3,4} }; // Does not compile
I understand how std::array works and the need for the double braces in general, but as I'm compiling for C++17 I expected brace elision to come into play.
Why is brace elision not applicable here?

std::array<std::vector<int>, 2> is effectively
struct array {
std::vector<int> elems[2];
};
elems is a subaggregate just fine. The issue is that per the language rules, if the initializer starts with a { it's always assumed that you aren't eliding braces; instead, {1, 2} is taken as the initializer of the entire subaggregate elems, attempting to initialize its first element with 1 and second element with 2 (this is obviously invalid - you can't convert an integer to a vector - but doesn't affect the interpretation), and {3, 4} is considered the initializer for the thing after elems - and since there are no such thing, it's another error.
Initializing the first element with something that's not a braced-init-list is sufficient to trigger brace elision:
std::array<std::vector<int>, 2> v = { std::vector<int>{1,2}, {3,4} };
Note that from a specification perspective, the library doesn't guarantee initialization of std::array<T, N> from anything other than another std::array<T, N> or a list of "up to N elements whose types are convertible to T". This notably excludes braced-init-lists because they have no type, and actually also disallows "double braces" because that's just a special case of having a single element that is a braced-init-list .
This is an area where we may have been better off specifying it with code. The core language rules defy easy specification in words and the implementation details will leak out - and have already done so.

As T.C. pointed out my original interpretation was not corret, brace elision is allowed see [dcl.init.aggr]p15:
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. ...
but std::array according to array.overview:
An array is an aggregate that can be list-initialized with up to N elements whose types are convertible to T.
which is not the case we have.

Related

Does designated initializer of sub-aggregate require curly braces?

In the following program, aggregate struct B has the field a, which is itself an aggregate. Can C++20 designated initializer be used to set its value without surrounding curly braces?
struct A { int i; };
struct B { A a; };
int main() {
[[maybe_unused]] B x{1}; //ok everywhere
[[maybe_unused]] B y{.a = {1}}; //ok everywhere
[[maybe_unused]] B z{.a = 1}; //ok in MSVC,Clang; error in GCC
}
MSVC and Clang compilers accept this code. But GCC issues a weird error:
error: 'A' has no non-static data member named 'a'
Demo: https://gcc.godbolt.org/z/65j1sTcPG
Is it a bug in GCC, or such initialization is not permitted by the standard?
TLDR; GCC is right, everyone else is wrong because they're pretending that designated initializer-lists act like equivalent non-designated initializer-lists all the time.
To understand what is happening here (and why compilers disagree), let's look at your first example:
B x{1};
Since we're using braces, the rules of list initialization kick in. The list is not a designated initializer list, so 3.1 fails. 3.2 fails because int is not of type B or a type derived from B. 3.3 fails fails because B isn't an array of characters. Finally 3.4 is followed, which takes us to aggregate initialization.
[dcl.init.aggr]/3.2 tells us that the explicitly initialized elements of B consist of B::a.
Paragraph 4 tells us how the explicitly initialized elements are initialized. 4.1 doesn't apply, as B is not a union. But also... 4.2 doesn't apply because B::a cannot be copy-initialized from 1.
That seems like it shouldn't work. Fortunately, paragraphs 15 and 16 exist:
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.
All implicit type conversions ([conv]) are considered when initializing the element with an assignment-expression.
If the assignment-expression can initialize an element, the element is initialized. Otherwise, if the element is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first element of the subaggregate.
That is, if the initializer cannot initialize A via copy-initialization, the rules of brace elision kick in. And A can be initialize from an initializer-list of {1}. Therefore, it is.
And this is where designated initializers have a problem. A designated-initializer-list is not an initializer-list. And therefore, the brace elision paragraphs do not apply.
And therefore, B z{.a = 1}; must fail.
The reason the other compilers don't catch this is likely the following. They probably implement designated initializers by stripping out the designations and inserting any default member initializers/value initialization between non-consecutive elements, then applying normal aggregate initializer rules. But that's not quite the same thing, since designated initializer lists don't participate in brace elision.
GCC is correct to reject this code: it attempts to copy-initialize z.a from 1 ([dcl.init.aggr]/4.2), which as the comments say doesn’t work. The brace elision that GCC seems to be envisioning to produce the diagnostic is, however, invalid: that just doesn’t exist for designated-initializer-lists.

C++20 initializing aggregates from a parenthesized list of values, not supporting inner array

C++20 adopted p0960 - allowing initialization of aggregates from a parenthesized list of values.
The exact wording ([dcl.init] 17.6.2.2) says:
[...] if no constructor is viable, the destination type is an aggregate
class, and the initializer is a parenthesized expression-list, the
object is initialized as follows.
Let e1 , …, en be the elements
of the aggregate ([dcl.init.aggr]).
Let x1, …, xk be the elements of the expression-list.
If k is greater than n, the program is ill-formed.
The element ei is copy-initialized with xi for 1
≤ i ≤ k . The remaining elements are initialized with their default
member initializers [...]
This doesn't allow initialization of inner array with parenthesized list of values:
struct Foo {
int i, j;
};
struct Moo {
int arr[2];
};
int main() {
// before C++20:
Foo foo1{1, 2};
// with C++20:
Foo foo2(1, 2); // p0960! we are good
// before C++20:
Moo moo1{1, 2};
// C++20 - oops p0960 doesn't help here:
Moo moo2(1, 2); // error: too many initializers
// before C++20:
std::array<int, 2> arr1{1, 2}; // OK
std::array<int, 2> arr2({1, 2}); // OK
std::array<int, 2> arr3{{1, 2}}; // OK
// C++20 - oops p0960 doesn't help here:
std::array<int, 2> arr4(1, 2); // error: too many initializers
}
The fact that std::array cannot be initialized with rounded brackets prevents it from participating in a generic code that creates an object of unknown type T from a list of values (e.g. an algorithm that uses make_shared, make_unique, make_from_tuple etc.).
Why p0960 didn't take a more simple approach making ()-initialization more like {}?
For example, something like:
if no constructor is viable, the destination type is an aggregate
class, and the initializer is a parenthesized expression-list, the
object would be initialized as if the values were sent with brace-initialization.
p0960 changed between r1 and r2:
r2: This revision changes the mental model from the original “literal rewrite to a braced list” to “as if a synthesized, explicit constructor with appropriate mem-initializers was called” 1. This has the effect of allowing narrowing conversions in the parenthesized list, even when narrowing conversions would be forbidden in the corresponding braced list syntax. It also clarifies the non-extension of temporary lifetimes of temporaries bound to references, the absence of brace elision, and the absence of a well-defined order of evaluation of the arguments.
The reason why this change was made can be found in the changed design principles for p0960:
r1: Parenthesized initialization and braced-initialization should be as similar as possible.
r2:
Parenthesized initialization and braced-initialization should be as similar as possible, but as distinct as necessary to conform with the existing mental models of braced lists and parenthesized lists.
(emphasis mine)
"Why p0960 didn't take a more simple approach making ()-initialization more like {}?":
When the decision to go for conformance with the existing mental models 1 had been taken, not allowing brace elision seems like the only approach.

List initialisation of two dimensional std::array [duplicate]

This question already has an answer here:
Why can't simple initialize (with braces) 2D std::array? [duplicate]
(1 answer)
Closed 2 years ago.
In the following program, I tried to initialise my 2x2-element std::array using the line which has nested list initialisation (currently commented out). MSVC2017 gave me a compiler error with "too many initializers". Why?
I then gave the non-nested list initialisation a go which worked. Why?
This appears to be inconsistent with initialisation of a nested std::vector. See third line of main(). What is going on here please?
#include <array>
#include <iostream>
#include <vector>
int main()
{
//std::array<std::array<int,2>,2> x = {{0,1},{2,3}}; // ERROR: TOO MANY INITIALIZERS
std::array<std::array<int,2>,2> x = {0,1,2,3}; // OK
std::vector<std::vector<int>> y = {{0,1},{2,3}}; // ALSO OK: NOT TOO MANY INITIALIZERS IF A std::vector?
}
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.
std::array is a little special. It's usually implemented as a wrapper of built-in arrays. Then for aggregate initialization, you need to add another pair of {}, as
std::array<std::array<int,2>,2> x = {{{0,1},{2,3}}};
On the other hand, std::array<std::array<int,2>,2> x = {0,1,2,3}; works because of brace elision.
If the aggregate initialization uses copy-list-initialization syntax (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.
If you specified nested initializer list, you have to specify the initializer list in the right layers. For this case, it should be 3 layers.

Default initialization of std::array?

With C++11 std::array, do I have the guarantee that the syntax std::array<T, N> x; will default-initialize all the elements of the array ?
EDIT: if not, is there a syntax that will work on all arrays (including zero-sized arrays) to initialize all elements to their default value?
EDIT: on cppreference, the default constructor description says:
(constructor) (implicitly declared) (public member function)
default-constructs or copy-constructs every element of the array
so the answer may be yes. But I would like to be sure of that according to the standard or future standard.
By definition, default initialization is the initialization that occurs when no other initialization is specified; the C++ language guarantees you that any object for which you do not provide an explicit initializer will be default initialized (C++11 §8.5/11). That includes objects of type std::array<T, N> and T[N].
Be aware that there are types for which default initialization has no effect and leaves the object's value indeterminate: any non-class, non-array type (§8.5/6). Consequently, a default-initialized array of objects with such types will have indeterminate value, e.g.:
int plain_int;
int c_style_array[13];
std::array<int, 13> cxx_style_array;
Both the c-style array and std::array are filled with integers of indeterminate value, just as plain_int has indeterminate value.
Is there a syntax that will work on all arrays (including zero-sized arrays) to initialize all elements to their default value?
I'm guessing that when you say "to their default value" you really mean "initialize all elements to T{}". That's not default-initialization, it is value-initialization (8.5/7). You can request value initialization quite easily in C++11 by giving each declaration an empty initializer:
int plain_int{};
int c_style_array[13]{};
std::array<int, 13> cxx_style_array{};
Which will value-initialize all of the array elements in turn, resulting in plain_int, and all the members of both kinds of arrays, being initialized to zero.
Default-initialization is a term from the Standard potentially meaning no initialization at all, so you probably mean zero-initialization.
The description at cppreference.com is actually a bit misleading. std::array is an aggregate class, and if the element type is primitive, it is POD: "plain old data," with semantics closely matching the C language. The implicitly-defined constructor of std::array< int, N > is a trivial one which does absolutely nothing.
Syntax like std::array< int, 3 >() or std::array< int, 3 > x{} which provide zeroed values do not do so by invoking a constructor. Getting zeroes is part of value-initialization, specified in C++11 §8.5/8:
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized …, and if T has a non-trivial default constructor, the object is default-initialized;
std::array has no user-provided default constructor, so it gets zero-initialized. It has an implicitly-defined default constructor, but it is trivial, so it is never default-initialized. (But this doesn't make a difference since trivial initialization by definition has no effect at runtime.)
if not, is there a syntax that will work on all arrays (including zero-sized arrays) to initialize all elements to their default value?
C-style arrays and std::array are both aggregates, and the way to completely zero-initialize any aggregate is with the syntax = {}. This works since C++98. Note that C-style arrays cannot have zero extent, and that sizeof (std::array< X, 0 >) is not zero.
Both T x[N]; and std::array<T, N> x; default-initialize every element of the array.
For example, if T = std::string, every element will be an empty string. If T is a class without a default constructor, both will fail to compile. If T = int, every element will have indeterminate value (unless that declaration happens to be at namespace scope)
C++11 std::array::fill is a good option for some cases.
First of all, T x[N] does default initialize the elements, although default initialization of a scalar type T actually does nothing. The above also holds for std::array x. I think what you need is list initialization.

Is this list-initialization of an array of unknown size valid in C++0x?

Is this list-initialization of an array of unknown size valid in C++0x?
int main() { int x[]{0, 1,2,3,4}; return x[0]; }
I believe it is valid, but would appreciate some confirmation.
If anyone could quote from the C++0x-FCD to support their case, it would be greatly appreciated.
Thanks!
This goes from 8.5/16 first bullet to 8.5.4 list-initialization and from 8.5.4/3 third bullet to 8.5.1 aggregate initialization and then 8.5.1/4 says
An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where shall be greater than zero, is defined as having elements
The only difference if the object is an array between = { ... } and { ... } is that the first is called copy-list-initialization and the second is called direct-list-initialization, so both are kinds of list-initialization. The elements of the array are copy-initialized from the elements of the initializer list in both cases.
Notice that there is a subtle difference between those forms if the array has a size and the list is empty, in which case 8.5.4 second bullet applies:
struct A {
explicit A();
};
A a[1]{}; // OK: explicit constructor can be used by direct initialization
A a[1] = {}; // ill-formed: copy initialization cannot use explicit constructor
This difference does not apply to lists that have content in which case third bullet applies again, though
struct A {
explicit A(int);
};
A a[1]{0}; // ill-formed: elements are copy initialized by 8.5.1
A a[1] = {0}; // ill-formed: same.
The FCD changed this compared to the previous draft, and initialization with an empty initializer list now always works even with explicit default constructors. This is because the FCD states that the elements are value-initialized, and value initialization doesn't care about explicitness since it doesn't do overload resolution on default constructors (it couldn't figure out better or worse matches anyway). The previous draft used normal overload resolution on the constructors and thus rejected explicit default constructors during copy initialization. This defect report did that change.
Yes, it is valid, and has been for decades, even in C. The size is simply set to the number of elements supplied. I don't know the reference, unfortunately.
(Added bonus...) If you need the number of elements use sizeof(x)/sizeof(*x). It's safer than hard-coding a constant that may become invalid if you add or remove entries.
EDIT: As pointed out in the comments, the code in question is missing an = (a fact that I missed), without which it isn't valid in any current standard of C or C++.