Consider this piece of code:
struct S {
float b;
int a;
};
int main() {
S s{{{}}};
return s.a;
}
Godbolt
Clang 6.0.0 compiles this code, but shows a warning:
<source> warning: too many braces around scalar initializer [-Wmany-braces-around-scalar-init]
GCC 8.2 doesn't compile this code and reports an error:
<source>: In function 'int main()':
<source>:9:10: error: braces around scalar initializer for type 'float'
Which one is correct? What does the specification say about this?
Both compilers are correct. Unless you violate a rule that says no diagnostic required the compiler should issue you a message. Whether that message is a warning or an error is up to the implementation. Normally you'll get a warning if it is something the compiler can still proceed with and an error when there is no way the compiler can continue.
What does the specification say about this?
dcl.init.aggr/3:
3 When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate. The explicitly initialized elements of the aggregate are determined as follows:
3.1 If the initializer list is a designated-initializer-list, the aggregate shall be of class type, the identifier in each designator shall name a direct non-static data member of the class, and the explicitly initialized elements of the aggregate are the elements that are, or contain, those members.
3.2 If the initializer list is an initializer-list, the explicitly initialized elements of the aggregate are the first n elements of the aggregate, where n is the number of elements in the initializer list.
3.3 Otherwise, the initializer list must be {}, and there are no explicitly initialized elements.
Related
Consider some type T (for simplicity, you may assume int) and some integral constant N, which we use to define an array like this:
T array[N]{}; // Note the empty braces here!
According to cppreference, value initialization is defined as follows:
This is the initialization performed when an object is constructed with an empty initializer.
But further down it is written:
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
But then a little bit more down, the following statement appears:
if T is an array type, each element of the array is value-initialized;
From my understanding, the first and third quoted statements contradict to the second one.
So my two questions are:
Is the code snippet above a value initialization or an aggregate initialization?
Do the three quoted statements really contradict or am I missing something?
Note: I've seen similar questions here but they all differ a bit in the specifics.
Is T array[N]{} a value initialization or aggregate initialization?
It is list initialization and part of this initialization process involves aggregate initialization as per dcl.init.list. Additionally, it is also direct list initialization as quoted below.
1) List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the initializer-list or designated-initializer-clauses of the designated-initializer-list are called the elements of the initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.
[Note 1 : List-initialization can be used
(1.1) as the initializer in a variable definition ([dcl.init])
...
— end note]
The above means that T array[N]{} is list-initialization.
Now let's move on to how the elements of the array is initialized which is given in dcl.init.list#3:
3) List-initialization of an object or reference of type T is defined as follows:
3.4) Otherwise, if T is an aggregate, aggregate initialization is performed.
And since in our example T array[N] is an aggregate, the above implies that in our example the whole process of initialization of the array T array[N] involves aggregate initialization.
Finally, from aggregate initialization given below, we will note that each element is copy-initialized from an empty initializer list:
3) When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate. The explicitly initialized elements of the aggregate are determined as follows:
3.3) Otherwise, the initializer list must be {}, and there are no explicitly initialized elements.
The above means that there are no explicitly initialized elements in our example so we move onto dcl.init.aggr#5:
5) For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
5.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list.
(emphasis mine)
Essentially, this means that each element of type T of the array will be initialized from an empty initializer list.
Note that this also explains why the following contrived example fails in C++20:
struct T
{
T() = delete;
};
int main()
{
T array[5]{}; //this fails as a consequence of above explanation
}
I'm trying to understand the difference between aggregate and list initialization. In particular it's mentioned here that aggregate initialization allows narrowing but that does not seem to be the case
#include <type_traits>
struct A {
int y;
};
static_assert(std::is_aggregate_v<A>);
int main() {
A a{10.0};
}
Error:
<source>: In function 'int main()':
<source>:10:9: error: narrowing conversion of '1.0e+1' from 'double' to 'int' [-Wnarrowing]
10 | A a{10.0};
| ^~~~
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:10:9: error: narrowing conversion of '1.0e+1' from 'double' to 'int' [-Wnarrowing]
10 | A a{10.0};
| ^~~~
Execution build compiler returned: 1
To answer your question very briefly.
I believe the author of the blog was talking about pre-C++11 standards. Until C++11 the narrowing conversions were possible, however they are now prohibited.
If you are compiling with g++, remove the #include and static_assert and run it with the -std=c++03 compile flag and the code will work (but should throw a warning). As of c++11 and above narrowing conversion is forbidden.
You can read more about aggregate initialization here:
https://en.cppreference.com/w/cpp/language/aggregate_initialization
Hope it helps! :)
The other answer is correct about the concrete behavior of the program you are showing, however to answer the question in your title directly:
List-initialization is any initialization with an initializer of the form {/*...*/} or = {/*...*/}.
(At least before C++20) Aggregate initialization is a specific kind of list-initialization which is used if the type that is being initialized is an an aggregate type (i.e. an array or a class without user-declared (*not exactly before C++20) constructors, only public non-static data members and no virtual functions). It is special to other forms of initialization, because it doesn't call a constructor and instead initializes subobjects of the aggregate one-by-one from the list of initializers in the braces.
Since C++20 there is also an an alternative form of aggregate initialization which uses parentheses, so that the above is maybe not exactly true anymore.
The rule that narrowing conversions are forbidden applies to all list-initialization, not specifically to aggregate initialization. And it doesn't apply to aggregate initialization with parentheses either.
All of this applies only since C++11. Before C++11 the only possible initialization with braces was aggregate initialization and narrowing was not forbidden.
When I'm trying to compile the following code, the compiler complains:
int main(void)
{
std::initializer_list<int> lst1{};
std::initializer_list<int> lst2{lst1}; // error
}
The compiler (gcc) gives me the following error:
error: could not convert '{lst1}' from '<brace-enclosed initializer list>' to 'std::initializer_list<int>'
But when I tried to use direct-initialization the program compiles fines:
std::initializer_list<int> lst2(lst1); // OK
Why this is well-formed? Why the compiler rejects the list-initialization and allows direct-initialization? Is there's a rule from the standard for that?
Aslo, Is the following code is well-formed? I mean, Can I do this:
int main(void)
{
std::initializer_list<int> lst1{};
std::initializer_list<std::initializer_list<int>> lst2{lst1}; //OK
}
?
The reason why you can't list-initialize a std::initializer_list<int> from the same type is that there is a special rule for list-initialization of a std::initializer_list<E>, which takes precedence over the other rules for list-initialization. The rule is [dcl.init.list]/3.6:
Otherwise, if T is a specialization of std::initializer_list<E>, the object is constructed as described below.
"Below" can only be referring to [dcl.init.list]/5 (and /6):
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation generated and materialized ([conv.rval]) a prvalue of type "array of N const E", where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. [...]
That means that when list-initializating std::initializer_list<E>, the only possible interpretation of such an initialization is that the elements of the braced-init-list are used to initialize the elements of the std::initialize_list<E> object. Even though this is ill-formed, the compiler cannot go back and try the next rule even though it might be well-formed (i.e., [dcl.init.list]/3.7, which can select a copy constructor).
[dcl.init.list]/5 also governs the meaning of the other initialization you ask about:
std::initializer_list<std::initializer_list<int>> lst2{lst1};
It is well-formed with an obvious meaning.
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.
How can one value-initialize aggregate types in C++14 with the list-intialization syntax?
Aggregate_t {};
This is seen as aggregate initialization, which produces errors or warnings for uninitialized members of Aggregate_t.
Is this possible at all?
EDIT: examples
struct Aggregate_t {
int x;
};
int main (int, char**)
{
Aggregate_t {};
return 0;
}
Compiling with g++-4.9.2:
main.c++: In function ‘int main(int, char**)’:
main.c++:7:16: warning: missing initializer for member ‘Aggregate_t::x’ [-Wmissing-field-initializers]
Aggregate_t {};
^
[dcl.init.aggr]:
7 - If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized [C++14: from its brace-or-equal-initializer or, if there is no brace-or-equal-initializer,] from an empty initializer list (8.5.4).
So g++ is being overzealous with its warning; I don't know of a way to avoid it while preserving it in cases where the warning is valid, except of course to use copy-initialization with expected copy elision:
Aggregate_t a = Aggregate_t();