这是标准中的疏忽吗?
它被认为是标准中的一个缺陷,跟踪为LWG #2089,由 C++20 解决。在那里,构造函数语法可以对聚合类型执行聚合初始化,只要提供的表达式不会调用复制/移动/默认构造函数。由于所有形式的间接初始化(push_back
、in_place
、make_*
等)都显式使用构造函数语法,因此它们现在可以初始化聚合。
在 C++20 之前,一个很好的解决方案是难以捉摸的。
根本问题来自这样一个事实,即您不能随便使用braced-init-lists。使用构造函数的类型的列表初始化实际上可以隐藏构造函数,使得某些构造函数无法通过列表初始化调用。这就是vector<int> v{1, 2};
问题所在。这将创建一个 2-element vector
,而不是唯一元素为 2 的 1-element 向量。
因此,您不能在通用上下文中使用列表初始化,例如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 分为允许聚合的组和不允许聚合的组的方式来解决。有许多可能的解决方案,但没有一个是理想的。