模板结构体的花括号初始化

11

我正在尝试创建一个链表模板,对于用户定义的类型它很好用,但是对于像int这样的基本类型,gcc和clang的行为不同。

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);
}

虽然clang编译这个代码没有问题,但gcc会生成以下错误消息。

错误:无法将'{nullptr,{args#0}}'从“<brace-enclosed initializer list>”转换为“Node<int>”

我知道如何解决这个问题,但我还是很感兴趣是否clang太宽容了,我不能依赖这个代码的可移植性,或者这是一个gcc的bug,应该在某个时候解决。
示例:https://godbolt.org/g/9gnvNQ

去掉大括号{args...}可以解决GCC和Clang的问题。 - Arnav Borborah
这个是有效的:return {nullptr, args... }; - Richard Critten
2
如果我去掉大括号,那么我将无法使用具有用户定义构造函数的结构体。 (https://godbolt.org/g/32ZR6K)我知道一般解决方案,但我对正确的编译器行为感兴趣。 - SteelRaven
1
建议您添加 [language-lawyer] 标签。 - Richard Critten
GCC不允许struct { int x; } v = { {1} };,但它允许int x = {1};。我认为这是GCC的一个错误。 - cpplearner
@cpplearner 对于这段代码,clang会发出警告“warning: braces around scalar initializer”,这让我觉得它并不是真正的干净代码,而且clang可能过于宽容了。如果有微软编译器的结果,我将不胜感激,但很遗憾我没有。 - SteelRaven
2个回答

4

这是一个GCC的bug。

首先,根据[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 ]

其次,根据[dcl.init.aggr]/1Node<int>是一个聚合体(aggregate):

聚合体是一个数组或者一个具有以下特征的类:

  • 没有用户自定义、显式或继承的构造函数([class.ctor])

  • 没有非静态私有或保护数据成员([class.access])

  • 没有虚函数

  • 没有虚、私有或保护基础类([class.mi])

因此进行了聚合初始化,并且根据[dcl.init.aggr]/4.2val递归地使用{args...}进行列表初始化:

否则,将从相应的初始化表达式或相应的指定初始化器子句花括号或等号初始化器中复制初始化元素。如果该初始化程序的形式为赋值表达式或= 赋值表达式,并且需要缩小转换以转换表达式,则程序无效。[注意:如果初始化器本身是一个初始化器列表,则元素将进行列表初始化,如果元素是聚合体,则会递归应用这个子句的规则。 ---结束语]

然后再次应用[dcl.init.list]/3.9
因此可以得出结论,此初始化是定义良好的。

1
我相信你想要的是这样的:

我相信您想要类似于这样的内容:

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

这将明确地使用提供的参数构造一个对象 T,并且适用于任何用户定义的类型,如下所示。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接