I am trying to create a linked list template and it works fine for user defined types, but for fundamental types like int behaviour of gcc and clang differ.
template<class T>
struct Node {
Node* next;
T val;
};
template<class T, class... Args>
Node<T> create(Args... args) {
return {nullptr, {args...}};
}
int main() {
create<int>(0);
}
While clang compiles that code without problems, gcc generates the following error message.
error: could not convert ‘{nullptr, {args#0}}’ from ‘<brace-enclosed initializer list>’ to ‘Node<int>’
While I know how to solve this problem, I am still interested whether clang is too permissive and I can't rely on portability of this code, or it is a gcc bug which should be resolved sometime.
Example: https://godbolt.org/g/9gnvNQ
If a class has non-default constructors, the order in which class members appear in the brace initializer is the order in which the corresponding parameters appear in the constructor, not the order in which the members are declared (as with class_a in the previous example).
When initializing a struct, the first initializer in the list initializes the first declared member (unless a designator is specified) (since C99), and all subsequent initializers without designators (since C99)initialize the struct members declared after the one initialized by the previous expression.
An initializer for a structure is a brace-enclosed comma-separated list of values, and for a union, a brace-enclosed single value. The initializer is preceded by an equal sign ( = ).
Uniform initialization is a feature in C++ 11 that allows the usage of a consistent syntax to initialize variables and objects ranging from primitive type to aggregates. In other words, it introduces brace-initialization that uses braces ({}) to enclose initializer values.
This is a GCC bug.
First, braces around scalar initializer (list-initialization for scalar type) are permitted according to [dcl.init.list]/3.9:
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed. [ Example:
int x1 {2}; // OK int x2 {2.0}; // error: narrowing
— end example ]
Second, Node<int>
is an aggregate according to [dcl.init.aggr]/1:
An aggregate is an array or a class with
no user-provided, explicit, or inherited constructors ([class.ctor]),
no private or protected non-static data members ([class.access]),
no virtual functions, and
no virtual, private, or protected base classes ([class.mi]).
So aggregate initialization is performed and val
is list-initialized with {args...}
recursively according to [dcl.init.aggr]/4.2:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or the brace-or-equal-initializer of the corresponding designated-initializer-clause. If that initializer is of the form assignment-expression or = assignment-expression and a narrowing conversion is required to convert the expression, the program is ill-formed. [ Note: If an initializer is itself an initializer list, the element is list-initialized, which will result in a recursive application of the rules in this subclause if the element is an aggregate. — end note ]
Then [dcl.init.list]/3.9 applies again.
As a conclusion, this initialization is well-defined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With