我正在尝试创建链接列表模板,它适用于用户定义的类型,但对于基本类型,如gcc和clang的int行为不同。
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会生成以下错误消息。
错误:无法将'&lt; brace-enclosed initializer list&gt;'中的'{nullptr,{args#0}}'转换为'Node&lt; int&gt;'
虽然我知道如何解决这个问题,但我仍然感兴趣的是clang是否过于宽松,我不能依赖这段代码的可移植性,或者它是一个gcc bug,应该在某个时候解决。
答案 0 :(得分:3)
这是一个GCC错误。
首先,根据[dcl.init.list]/3.9,允许围绕标量初始值设定项(标量类型的列表初始化):
否则,如果初始化列表具有E类型的单个元素且T不是引用类型或其引用类型与E引用相关,则从该元素初始化对象或引用(通过复制初始化为copy-list-initialization,或直接初始化直接列表初始化);如果需要缩小转换(见下文)将元素转换为T,则程序格式不正确。 [实施例:
int x1 {2}; // OK int x2 {2.0}; // error: narrowing
- 结束示例]
其次,Node<int>
是根据[dcl.init.aggr]/1的汇总:
聚合是一个数组或带有
的类
没有用户提供的,显式的或继承的构造函数([class.ctor]),
没有私有或受保护的非静态数据成员([class.access]),
没有虚拟功能,
没有虚拟,私有或受保护的基类([class.mi])。
因此,根据[dcl.init.aggr]/4.2递归执行聚合初始化并val
以{args...}
进行列表初始化:
否则,该元素是从相应的 initializer-clause 或相应的指定初始化器的 brace-or-equal-initializer 复制初始化的 - 条款。如果该初始化程序的格式为 assignment-expression 或= assignment-expression ,并且转换表达式需要缩小转换,则程序格式错误。 [注意:如果初始化程序本身是初始化程序列表,则该元素是列表初始化的,如果该元素是聚合,则将导致本子条款中规则的递归应用。 - 结束说明]
然后[dcl.init.list]/3.9再次适用。
总之,这种初始化是明确定义的。
答案 1 :(得分: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;
}