为什么聚合结构可以用大括号初始化,但不能使用与大括号初始化相同的参数列表来放置?

Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?

好像是this code:

#include <string>
#include <vector>

struct bla
{
    std::string a;
    int b;
};

int main()
{
    std::vector<bla> v;
    v.emplace_back("string", 42);
}

在这种情况下可以正常工作,但它不能(我明白为什么)。给 bla 一个构造函数解决了这个问题,但消除了类型的聚合性,这可能会产生深远的影响。

这是对标准的疏忽吗?还是我错过了某些情况,这些情况会在我脸上爆炸,或者它没有我想的那么有用?

23.2.1/15.5

T is EmplaceConstructible into X from args, for zero or more arguments args, means that the following expression is well-formed:

allocator_traits<A>::construct(m, p, args)

23.2.1/15

[Note: A container calls allocator_traits<A>::construct(m, p, args) to construct an element at p using args. The default construct in std::allocator will call ::new((void*)p) T(args), but specialized allocators may choose a different definition. —end note ]

因此,默认分配器使用构造函数,更改此行为可能会导致向后兼容性丢失。您可以在这个答案 .

中阅读更多内容

还有一个关于它的未来的问题"Towards more perfect forwarding" and some random discussion

Is this an oversight in the Standard?

它被认为是标准中的缺陷,跟踪为 LWG #2089,已由 C++20 解决。在那里,构造函数语法可以对聚合类型执行聚合初始化,只要提供的表达式不会调用 copy/move/default 构造函数。由于所有形式的间接初始化(push_backin_placemake_* 等)都显式使用构造函数语法,因此它们现在可以初始化聚合。

C++20 之前的版本,一个好的解决方案是难以捉摸的。

根本的问题在于你不能随意使用braced-init-lists。使用构造函数的类型的列表初始化实际上可以隐藏个构造函数,这样某些构造函数就无法通过列表初始化来调用。这就是 vector<int> v{1, 2}; 问题。这将创建一个 2 元素 vector,而不是一个只有 2 个元素的 1 元素向量。

因此,您不能在 allocator::construct.

等通用上下文中使用列表初始化

这将我们带到:

I would think there's be a SFINAE trick to do that if possible, else resort to brace init that also works for aggregates.

这需要使用 C++17 的 is_aggregate 类型特征。但这有一个问题:你必须将这个 SFINAE 技巧传播到 all 使用间接初始化的地方。这包括 any/variant/optionalin_place 构造函数和安置、make_shared/unique 调用等,其中 none 使用 allocator::construct.

并且这不包括需要此类间接初始化的用户代码。如果用户不进行与 C++ 标准库相同的初始化,人们会很不高兴。

这是一个棘手的问题,需要以不将间接初始化 API 分为允许聚合的组和不允许聚合的组的方式来解决。有many possible solutions个,其中none个比较理想

语言解决方案是最好的。