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

rub*_*nvb 10 c++ aggregate c++11 emplace

好像这段代码:

#include <string>
#include <vector>

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

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

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

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

DAl*_*Ale 5

23.2.1/15.5

T是来自args的EmplaceConstructible到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),但专用分配器可以选择不同的定义. - 尾注]

因此,默认分配器使用构造函数,更改此行为可能会导致向后兼容性丢失.您可以在此答案中阅读更多内容/sf/answers/614810311/.

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


Nic*_*las 5

这是标准中的疏忽吗?

它被认为是标准中的一个开放缺陷,跟踪为LWG#2089.但是一个很好的解决方案是难以捉摸的.

根本问题来自这样一个事实:你不能只是毫不犹豫地使用braced-init-lists.具有构造函数的类型的列表初始化实际上可以隐藏构造函数,这样某些构造函数就无法通过列表初始化进行调用.这是vector<int> v{1, 2};问题所在.这创建了一个2元素vector,而不是1元素向量,其唯一元素是2.

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

这让我们:

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

这需要一种is_aggregate类型特征.目前还不存在,没有人提出它的存在.哦,当然,你可以使用is_constructible,因为该问题的拟议解决方案.但是有一个问题:它有效地创建了list-initilaization的替代方案.

考虑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);
Run Code Online (Sandbox Code Playgroud)

这些做了两件完全不同的事情.在这种fvec情况下,它执行列表初始化,因为vector<float>不能从两个浮点数构造.在这种ivec情况下,它调用一个构造函数,因为它vector<int>可以从两个整数构造.

因此,您需要将列表初始化限制allocator::construct为仅T在聚合时才起作用.

即使你这样做,你也必须将这个SFINAE技巧传播到所有使用间接初始化的地方.这包括any/variant/optionalin_place建设者和炮位,make_shared/unique电话等,其中没有使用allocator::construct.

并且这不计算需要这种间接初始化的用户代码.如果用户没有进行与C++标准库相同的初始化,那么人们会感到不安.

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