11
votes

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

2
Removing the braces {args...} solves the problem for GCC and Clang.Arnav Borborah
This worked: return {nullptr, args... };Richard Critten
If I remove braces then i wouldn't be able to work with structures with user defined constructors. (godbolt.org/g/32ZR6K) I know the general solution, but I am interested in what a correct compiler behaviour should be.SteelRaven
Suggest you add the [language-lawyer] tag .Richard Critten
GCC does not allow struct { int x; } v = { {1} }; though it does allow int x = {1};. I think it's a bug of GCC.cpplearner

2 Answers

3
votes

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.

1
votes

I believe you want something like this:

return {nullptr, T{args...}};

This explicitly constructs an object T using the arguments provided and works with any user defined type as well, shown in the following

template<class T>
struct Node {
    Node* next;
    T val;
};

template<class T, class... Args>
Node<T> create(Args... args) {
    return {nullptr, T{args...}};
}

struct Foo {
    string s;
    int i;
};

int main() {
    auto n = create<Foo>("foo", 42);
    cout << n.val.s << ' ' << n.val.i << endl;
}