为什么聚合结构可以大括号初始化,但不能使用与大括号初始化中相同的参数列表进行放置?
Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?
似乎是这样的代码:
#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_back
、in_place
、make_*
等)都显式使用构造函数语法,它们现在可以初始化聚合。
在 C++20 岁之前,一个好的解决方案是难以捉摸的。
根本问题来自这样一个事实,即您不能随意使用大括号的初始化列表。使用构造函数对类型进行列表初始化实际上可以隐藏构造函数,因此某些构造函数可能无法通过列表初始化调用。这是vector<int> v{1, 2};
问题。这会产生一个 2 元素vector
,而不是一个唯一元素是 2 的 1 元素向量。
因此,您不能在通用上下文(如allocator::construct
)中使用列表初始化。
这给我们带来了:
我认为如果可能的话,有一个 SFINAE 技巧可以做到这一点,否则求助于也适用于聚合的支撑 init。
这将需要使用 C++17 中的is_aggregate
型特征。但是有一个问题:然后你必须将这个SFINAE技巧传播到所有使用间接初始化的地方。这包括any/variant/optional
的in_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 的答案中阅读更多内容。
还有一个问题"迈向更完美的转发"和一些关于它未来的随机讨论。
- C++转换参数初始化问题
- 构造函数在退出函数时无法初始化一个参数
- 我使用向量来创建类对象列表.初始化向量时如何使用参数调用构造函数?
- c++构造函数成员初始化:传递参数
- 模板参数列表中的 false 在模板初始化期间计算为什么?
- 构造函数/函数声明参数列表中的统一初始化
- 在构造函数中使用可变参数初始化 std::tuple
- 副本初始化的默认模板参数推导
- 使用向量初始化参数化构造函数的对象数组
- 使用初始化参数的模板类型
- 为什么我的值没有由我的初始化(参数化)构造函数初始化?
- 在声明C 期间具有值的类方法的初始化参数
- 初始化参数时会发生什么?C
- 为什么在C++调用函数时无法初始化参数?
- C++中具有初始化参数的构造函数
- 初始化私有成员数组w/size作为类初始化参数
- 初始化参数 1..[-允许]
- 如何初始化参数化模板类的静态成员
- 在c++中初始化参数化类型数组
- 为什么在使用空引用参数时崩溃,而不是在初始化参数时崩溃?