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

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

本文关键字:初始化 参数 列表 结构 为什么 但不能      更新时间:2023-10-16

似乎是这样的代码:

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

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

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

这是标准中的疏忽吗?

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

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

根本问题来自这样一个事实,即您不能随意使用大括号的初始化列表。使用构造函数对类型进行列表初始化实际上可以隐藏构造函数,因此某些构造函数可能无法通过列表初始化调用。这是vector<int> v{1, 2};问题。这会产生一个 2 元素vector,而不是一个唯一元素是 2 的 1 元素向量。

因此,您不能在通用上下文(如allocator::construct)中使用列表初始化。

这给我们带来了:

我认为如果可能的话,有一个 SFINAE 技巧可以做到这一点,否则求助于也适用于聚合的支撑 init。

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

这还不包括需要这种间接初始化的用户代码。如果用户不执行与C++标准库相同的初始化,人们会感到不安。

这是一个棘手的问题,需要解决的方式是不会将间接初始化 API 分为允许聚合的组和不允许聚合的组。有许多可能的解决方案,但没有一个是理想的。

语言解决方案是最好的。

23.2.1/15.5

T 是 EmplaceConstructible from args 到 X,用于零个或多个参数 args,表示以下表达式的格式正确:

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

23.2.1/15

[注意:容器调用allocator_traits<A>::construct(m, p, args)以使用 args 在 p 处构造一个元素。std::allocator中的默认构造将调用::new((void*)p) T(args),但专门的分配器可以选择不同的定义。—尾注 ]

因此,默认分配器使用构造器,更改此行为可能会导致向后兼容性丢失。您可以在 https://stackoverflow.com/a/8783004/4759200 的答案中阅读更多内容。

还有一个问题"迈向更完美的转发"和一些关于它未来的随机讨论。