考虑以下计划:
struct X
{
X(int, int) { }
X(X&&) { }
};
int main()
{
X x( {0, 1} ); // Doesn't compile on ICC 13.0.1, compiles on
// Clang 3.2, GCC 4.7.2, and GCC 4.8.0 beta.
}
使用GCC 4.7.2,GCC 4.8.0和Clang 3.2编译时,该程序执行以下操作(*):
X
类型的临时文件,将值0
和1
传递给构造函数,然后; X
。使用ICC 13.0.1,它不会编译。
问题#1:谁是对的?
(*)实际上,删除了临时创建和移动构造函数的调用,但是使用-fno-elide-constructors
选项进行编译并向构造函数添加一些打印输出显示这是正在发生的事情上。
现在考虑以下内容,上述程序略有不同,其中统一初始化用于直接初始化x
:
int main()
{
X x{ {0, 1} }; // ERROR! Doesn't compile.
// ^........^
}
我不希望在这里使用大括号而不是括号来改变任何东西,但它以某种方式做到了:这个程序不能在我测试过的任何编译器上编译(Clang) 3.2,GCC 4.7.2,GCC 4.8.0 beta和ICC 13.0.1)。
问题#2:为什么?
答案 0 :(得分:2)
这只是所有编译器中的一个错误。 §8.5.4/ 3说,
对象或类型T的引用的列表初始化定义如下:
- 如果初始化列表没有元素,而T是具有默认构造函数的类类型,则对象为 值初始化。
- 否则,如果T是聚合,则执行聚合初始化(8.5.1)。
- 否则,如果T是std :: initializer_list的特化,则如下所述构造initializer_list对象并用于初始化对象......
- 否则,如果T是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数。如果转换任何参数需要缩小转换(见下文),则程序格式不正确。
- 否则,如果T是引用类型,则由T引用的类型的prvalue临时列表进行列表初始化,并且引用绑定到该临时类型。
- 否则,如果初始化列表具有单个元素,则从该元素初始化对象或引用;如果需要缩小转换(见下文)将元素转换为T,则程序格式不正确。
...
有相当多的案例。请注意,您实际上是列表初始化绑定到右值引用的prvalue临时对象。
对于GCC,我认为它正在尝试应用上面提到的最后一项,用于单元素初始化列表。如果我将构造函数签名更改为X(X&&, int = 3)
,则初始化程序{ {0, 1} }
会失败,但{ {0, 1}, 3 }
会成功。单个项目应该成功,因为因为元素是一个braced-init-list,我相信这个案例应该允许额外的括号,类似于parens。但失败与GCC的支撑缺陷的其他缺点类似。
我的高级别印象是,当编译器试图将列表视为具有类型的对象时,问题就出现了,但事实并非如此。很难将其转换回类似括号的参数列表。
更具体地查看错误消息(感谢LWS链接),
ICC坚持要求表达。这是错误的,因为根据基本语法,braced-init-lists可以包含其他braced-init-lists,而不仅仅是表达式。
Clang说“候选构造函数不可行:无法将初始化列表参数转换为'X'”但如果转换是显式的,则使用X x{ X{0, 0 } };
。这没有意义。它不是转换,因为列表没有要转换的类型。这是列表初始化。
GCC说“没有已知的转换参数1从''到'X&&'”表明它甚至没有达到定义临时绑定到参考资料。与Clang一样,它似乎尝试虚假转换,并指定X{0,0}
修复它。