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.
Related
I was under the impression that the following should become valid code under the new C++20 standard:
struct Foo
{
int a, b;
};
template<Foo>
struct Bar
{};
Bar<{.a=1, .b=2}> bar;
Yet, gcc 10.2.0, with -std=c++20 set complains: could not convert ‘{1, 2}’ from ‘<brace-enclosed initializer list>’ to ‘Foo’ and Clang cannot compile this snippet either. Can someone point out why it is not well formed?
This template-argument
{.a=1, .b=2}
is not allowed according to the grammar for a template-argument which only allows the following constructs:
template-argument:
constant-expression
type-id
id-expression
A brace-init list is not any of the above constructs, it's actually an initializer and so it cannot be used as a template-argument.
You can be explicit about the type of the object that you use as the template-argument:
Bar<Foo{.a=1, .b=2}> bar;
and this will work, since this is a constant-expression.
It's a C++ grammar thing. Things used for template arguments must be either type-ids, id-names, or constant-expressions.
And a braced-init-list is not an expression of any kind. They can only grammatically be present in a small number of places, specifically those used to initialize an object or variable.
In the past, it wasn't really relevant, since there wasn't much reason to use a braced-init-list to initialize the few valid NTTPs. Obviously that's changed, so this is something of an oversight. But this is what the C++20 standard says.
I was under the impression that the following should become valid code under the new C++20 standard:
struct Foo
{
int a, b;
};
template<Foo>
struct Bar
{};
Bar<{.a=1, .b=2}> bar;
Yet, gcc 10.2.0, with -std=c++20 set complains: could not convert ‘{1, 2}’ from ‘<brace-enclosed initializer list>’ to ‘Foo’ and Clang cannot compile this snippet either. Can someone point out why it is not well formed?
This template-argument
{.a=1, .b=2}
is not allowed according to the grammar for a template-argument which only allows the following constructs:
template-argument:
constant-expression
type-id
id-expression
A brace-init list is not any of the above constructs, it's actually an initializer and so it cannot be used as a template-argument.
You can be explicit about the type of the object that you use as the template-argument:
Bar<Foo{.a=1, .b=2}> bar;
and this will work, since this is a constant-expression.
It's a C++ grammar thing. Things used for template arguments must be either type-ids, id-names, or constant-expressions.
And a braced-init-list is not an expression of any kind. They can only grammatically be present in a small number of places, specifically those used to initialize an object or variable.
In the past, it wasn't really relevant, since there wasn't much reason to use a braced-init-list to initialize the few valid NTTPs. Obviously that's changed, so this is something of an oversight. But this is what the C++20 standard says.
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.
I am using the following code in VS2013 and it compiles.
explicit QIcon(const QString &fileName); // file or resource name
void setWindowIcon(const QIcon &icon);
I call the function like this:
setWindowIcon({ "icon.png" });
However in Clang 3.7.1 it fails with:
error chosen constructor is explicit in copy-initialization
I read in other questions that in the C++ standard, §13.3.1.7 [over.match.list], the following is stated:
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
Is VS2013 wrong in allowing this code to compile?
Yes, VS2013 is wrong in allowing the code to compile.
The important rule is in [over.ics.list] (quote from N3337):
[over.ics.list]/1]: When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting
it to a parameter type.
[over.ics.list]/3]: Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor of X to perform the initialization of an object of type X from the argument initializer list, the
implicit conversion sequence is a user-defined conversion sequence. If multiple constructors are viable but
none is better than the others, the implicit conversion sequence is the ambiguous conversion sequence. User-defined conversions are allowed for conversion of the initializer list elements to the constructor parameter
types except as noted in 13.3.3.1.
13.3.3.1 outlines implicit conversion sequences, which references [class.conv.ctor] regarding user-defined conversions:
[class.conv.ctor]/1: A constructor declared without the function-specifier explicit specifies a conversion from the types of its
parameters to the type of its class. Such a constructor is called a converting constructor.
So the constructor must not be marked explicit if it should be used for this form of initialization.
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();