这是标准中的疏忽吗?
它被认为是标准中的缺陷,跟踪为LWG #2089,已由C++20解决。在那里,构造函数语法可以对聚合类型执行聚合初始化,只要提供的表达式不会调用复制/移动/默认构造函数。由于所有形式的间接初始化(push_back、in_place、make_* 等)都显式使用构造函数语法,因此它们现在可以初始化聚合。
C++20 之前,一个很好的解决方案是难以捉摸的。
根本问题在于你不能随便使用braced-init-lists。带有构造函数的类型的列表初始化实际上可以隐藏构造函数,这样某些构造函数可能无法通过列表初始化来调用。这是vector<int> v{1, 2}; 问题。这将创建一个 2 元素 vector,而不是唯一元素为 2 的 1 元素向量。
因此,您不能在像allocator::construct 这样的通用上下文中使用列表初始化。
这让我们:
如果可能的话,我认为有一个 SFINAE 技巧可以做到这一点,否则请使用同样适用于聚合的大括号 init。
这需要is_aggregate 类型特征。目前不存在,也没有人提出它的存在。哦,当然,您可以使用is_constructible,正如针对该问题的提议解决方案所述。但这样做有一个问题:它有效地创建了列表初始化的替代方案。
考虑之前的vector<int> 示例。 {1, 2} 被解释为两个元素 initializer_list。但是通过emplace,它将被解释为调用两个整数构造函数,因为来自这两个元素的is_constructible 将是真的。这导致了这个问题:
vector<vector<float>> fvec;
fvec.emplace(1.0f, 2.0f);
vector<vector<int>> ivec;
ivec.emplace(1, 2);
它们做了两个完全不同的事情。在fvec 的情况下,它执行列表初始化,因为vector<float> 不能从两个浮点数构造。在ivec 的情况下,它调用构造函数,因为vector<int> 可以从两个整数构造。
所以您需要将allocator::construct 中的列表初始化限制为仅在T 是聚合时才起作用。
即使你这样做了,你也必须将这个 SFINAE 技巧传播到 所有 使用间接初始化的地方。这包括any/variant/optional 的in_place 构造函数和位置、make_shared/unique 调用等等,其中没有一个使用allocator::construct。
这还不包括需要这种间接初始化的用户代码。如果用户没有像 C++ 标准库那样做同样的初始化,人们会很不高兴。
这是一个棘手的问题,要以一种不将间接初始化 API 分为允许聚合的组和不允许聚合的组的方式来解决。有many possible solutions,没有一个是理想的。