Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Brace initialization of template struct

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

like image 783
SteelRaven Avatar asked Feb 03 '18 17:02

SteelRaven


People also ask

What is brace initialization?

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).

How do you initialize a struct in CPP?

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.

How do you initialize a structure?

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 ( = ).

What is uniform initialization in C++?

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.


1 Answers

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.

like image 78
xskxzr Avatar answered Oct 06 '22 09:10

xskxzr