转发就地构建和列表初始化

Forwarded in-place construction and list-initialization

转发就地建设,我认为是指std::allocator::construct and the various emplace methods, e.g., std::vector::emplace_back。我只是发现 C++ 中的转发就地构造没有(不能?)利用列表初始化语法。结果,似乎永远无法转发就地构建聚合。我只想确定转发就地构造是否不支持列表初始化,因此不支持聚合类型。这是由于语言的限制吗?有人可以提供有关此问题的标准参考吗?下面是一个例子:

虽然我们可以像

一样直接就地构建
int(*p)[3] = ...;
new(p) int[3]{1, 2, 3};

我们不能像

那样进行转发就地构建
std::allocator<int[3]> allo;
allo.construct(p, 1, 2, 3);

虽然{}被称为统一的初始化语法,但远非通用。

举这两个例子:

size_t a = 3;
size_t b = 1;
std::vector<size_t> v1{a,b};
std::vector<size_t> v2(a,b);

在第一种情况下,我们构造一个包含两个元素的向量,31

在第二种情况下,我们创建一个向量,其中包含 1,1,1 -- 1.

的 3 个副本

live example.

因此,在某些情况下,基于 {} 的构造可能会导致与基于 () 的构造不同的行为。此外,在上述情况下,无法使用 {} 构造(据我所知)达到“1 的 3 个副本”语法。但是 {3,2} 情况可以通过简单地显式创建初始化列表并将其传递给 ().

来处理

由于大多数采用初始化列表的类型都可以通过显式传入初始化列表来构建,并且 C++ 标准库是为具有构造函数的类型设计的,而不是为没有构造函数的类型设计的,因此 C++ 标准库几乎统一使用() 而不是 {}.

缺点是无法通过此机制放置要列表初始化的类型。

理论上,可以将使用 {} 构建的 list_emplace 方法添加到每个接口。我鼓励你提出这个建议!

std::allocatorconstruct()(以及 std::allocator_traits 提供的默认实现)指定使用 ()::new((void *)p) U(std::forward<Args>(args)...)(参见 [allocator.members]/p12, [allocator.traits.members]/p5).

此时将其更改为 {} 是不切实际的,因为它会默默地破坏现有代码:

std::vector<std::vector<int>> foo;
foo.emplace_back(10, 10); // add a vector of ten 10s with (); two 10s with {}

如果 () 不起作用,可以使用 an LWG issue 回退到使用 {}。我们必须看看委员会是否同意这个方向。

@Yakk 指出了这种方法的潜在缺点:

foo.emplace_back(10); // ten 0s
foo.emplace_back(10, 10); // ten 10s
foo.emplace_back(10, 10, 10); // three(!) 10s

一个类似的问题(参见 N2215 的附录 B)导致决定列表初始化将始终首选 initializer_list 构造函数。