4

使用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 聚合初始化来检测缩小转换?

4

2 回答 2

7

Paren-initializing 聚合允许缩小转换。

构造函数和聚合初始化的行为不同,该功能看起来像构造函数调用,因此有意设计为尽可能像构造函数调用。聚合初始化的所有显着特征(缩小转换、引用的生命周期延长等)在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;
};

它本身不会对我今天尝试的任何编译器发出警告。

于 2019-04-08T12:17:48.250 回答
5

使用 () 初始化时,您不应该想要“限制狭窄”。

这个特性的要点是允许在转发场景中使用聚合,例如container::emplace,就地构造函数等。它们目前不起作用,因为它std::allocator_traits<>::construct不会也不能使用列表初始化语法,因为在太多情况下它会隐藏您可能想要调用的构造函数。

在转发场景中,在处理聚合时,准确剔除缩小转换的能力是有限的。为什么?考虑一下:

struct Agg { int i; };

Agg a{5.0f};

这不是缩小转换,因为可以将特定的浮点文字值转换为 anint而不会损失精度。但是,当您通过转发构造emplace等时,编译器无法看到参数的实际值。它只看到类型。因此,如果您要这样做:

vector<Agg> v;
v.emplace_back(5.0f);

编译器所看到的只是emplace_back将尝试将 a 传递floatAgg. 这始终是一个狭窄的转换,因此始终是非法的。

列表初始化的缩小预防是有道理的,因为花括号初始化列表最好在本地使用。被初始化的类型是已知的,任何文字值都将直接在{}使用的地方提供。因此,有足够的信息以合理的方式处理缩小范围的问题。

一旦您开始转发,这将无法正常工作。缩小预防将剔除那些在本地值本来可以的参数。

所以问题是这样的:对于所有有效的 X、Y 和 Z,你想像emplace(X, Y, Z)以前一样工作吗?Agg{X, Y, Z}如果答案是肯定的,那么()聚合初始化不能防止缩小。

于 2019-04-10T17:21:54.960 回答