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.
Related
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.
I'm using the clang compiler (c++ 11 I think) that comes with RAD studio 10.2. By mistake I discovered today that the first n members of a struct or array can be assigned using the usual curly brackets e.g.
int a[500]={1};
struct {int a,b,c;} st={2,3};
The above compiles and works fine but I've never come across this or seen it used before and I can find no mention of it online (maybe I'm searching using the wrong type of wording). Is this c++ documented?
For aggregate initialization,
(emphasis mine)
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).
That means, for int a[500]={1};, the 1st element is initialized to 1 and the remaining 499 elements of the array are value-initialized to 0. For struct {int a,b,c;} st={2,3};, the member a is initialized to 2 and b is initialized to 3, the last member c is value-initialized to 0 too.
I've never come across this or seen it used before and I can find no mention of it online (maybe I'm searching using the wrong type of wording). Is this c++ documented?
Yes, this is documented. This syntax is called list initialisation - and more specifically since the types are aggregates: This is aggregate initialisation.
Initialise first n members only of struct or array
It is not possible to only initialise some members / elements. If you list initialise class object or array, then all of it will be initialised. The members / elements which lack an initialiser will be value initialised.
If you wanted to do that, then what you can do instead is default initialise the class object or array, and then initialise the sub objects selectively afterwards.
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.
This question already has answers here:
Brace elision in std::array initialization
(2 answers)
Closed 6 years ago.
In order to initialize a std::array with some values, you need to use this approach:
std::array<int,3> an_array{{3,4,5}};
I am aware of the reason that we need two curly braces (one for std::array and the the other for the inner c-style array).
My question: Why, by standard, std::array does not contain an initializer-list constructor that directly initialize the inner c-style array? Is not more eyes-friendly to be initialized as:
std::array<int,3> an_array{3,4,5};
Edit:
This information is from http://en.cppreference.com/w/cpp/container/array. I thought my compiler is allowing the second version directly as non-standard extension. Now, I am not even sure what is the standard exactly about this case.
// construction uses aggregate initialization
std::array<int, 3> a1{ {1, 2, 3} }; // double-braces required in C++11 (not in C++14)
The standard defines std::array as follows (N3337 for C++11, but the quoted parts are identical in N4140):
§23.3.2.1 [array.overview]/2
An array is an aggregate that can be initialized with the syntax
array<T, N> a = { initializer-list };
and an aggregate is defined as:
§8.5.1 [dcl.init.aggr]/1
An aggregate is an array or a class with no user-provided
constructors, no private or protected non-static data members, no base
classes, and no virtual functions.
So it cannot have a user-defined constructor, which an initializer_list one would be.
Additionally, C++11 defines brace elision only for the T x = { a } syntax:
§8.5.1 [dcl.init.aggr]/11
In a declaration of the form
T x = { a };
braces can be elided in an initializer-list as follows. [...]
whereas C++14 (N4140) lifts this requirement:
§8.5.1 [dcl.init.aggr]/11
Braces can be elided in an initializer-list as follows. [...]
So the following is perfectly valid C++14 and above:
std::array<int,3> an_array{3,4,5}
When I use an initializer list to create a struct, but the initializer list contains fewer elements than my struct, I see the remaining elements are initialized with zeroes.
Is this an undefined behaviour and I'm seeing zeroes because my compiler (VS2015) decided to zero the memory for me?
Or could someone point me to the documentation that explains this behaviour in C++?
This is my code:
struct Thing {
int value;
int* ptr;
};
void main() {
Thing thing { 5 };
std::cout << thing.value << " " << thing.ptr << std::endl;
}
And this is what it prints:
5 00000000
That last element is the one that got zeroed without an initializer.
This is defined behaviour. According to the rule of aggregate initialization, the remaining member ptr will be value-initialized, i.e. zero-initialized to NULL pointer.
(emphasis mine)
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.