使用P0960“允许从带括号的值列表中初始化聚合”,您也可以使用()
s进行聚合init。
但是,这种初始化允许缩小,而{}
s则不然。
#include <vector>
#include <climits>
struct Foo
{
int x, y;
};
int main()
{
// auto p = new Foo{INT_MAX, UINT_MAX}; // still won't compile
auto q = new Foo(INT_MAX, UINT_MAX); // c++20 allows narrowing aggregates init
std::vector<Foo> v;
// v.emplace_back(Foo{INT_MAX, UINT_MAX}); // still won't compile
v.emplace_back(INT_MAX, UINT_MAX); // c++20 allows narrowing aggregates init
// in furtherly perfect forwardings
}
是否可以通过括号使用C ++ 20聚合初始化来检测缩小转换?
Paren初始化聚合允许缩小转换。
构造函数和聚合初始化的行为不同,并且该特性看起来像构造函数调用,因此它被有意设计为尽可能像构造函数调用。在paren-initialization案例中,聚合初始化的所有显着特征(缩小的转换,引用的生命周期扩展等)非常有意地不存在。
唯一的区别是paren-initializing聚合确实从左到右评估表达式(而对于构造函数调用,我们对参数进行了不确定的评估)。
特别:
auto q = new Foo(INT_MAX, UINT_MAX);
将表现得好像你实际上已经编写了这个构造函数:
struct Foo
{
Foo(int x, int y) : x(x), y(y) { } // ~ish
int x, y;
};
哪个本身不会对我今天尝试的任何编译器发出警告。
使用()初始化时,您不应该“限制狭窄”。
此功能的要点是允许在转发方案中使用聚合,例如container::emplace
,就地构造函数等。它们目前不起作用,因为std::allocator_traits<>::construct
不会也不能使用列表初始化语法,因为有太多的情况会隐藏你可能想要调用的构造函数。
在转发方案中,在处理聚合时,准确剔除缩小转换的能力是有限的。为什么?考虑一下:
struct Agg { int i; };
Agg a{5.0f};
这不是一个缩小的转换,因为特定的浮点字面值可以转换为int
而不会损失精度。但是,当您通过emplace
等转发构造时,编译器无法看到参数的实际值。它只看到类型。所以,如果你这样做:
vector<Agg> v;
v.emplace_back(5.0f);
所有编译器都看到emplace_back
将尝试将float
传递给Agg
的聚合初始化。这总是一个缩小的转换,因此总是非法的。
列表初始化的缩小预防是有道理的,因为braced-init-lists最好在本地使用。正在初始化的类型是已知的,任何文字值都将直接在使用{}
的地方提供。因此,有足够的信息以合理的方式处理缩小问题。
一旦你进入转发,这只是不起作用。缩小预防将剔除参数,这些参数的值在本地会很好。
所以问题是这样的:对于所有有效的X,Y和Z,你想要emplace(X, Y, Z)
和Agg{X, Y, Z}
一样工作吗?如果答案是肯定的,那么()
聚合初始化不能阻止缩小。