为什么聚合结构可以用大括号初始化,但不能使用与大括号初始化相同的参数列表来放置?
Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?
好像是this code:
#include <string>
#include <vector>
struct bla
{
std::string a;
int b;
};
int main()
{
std::vector<bla> v;
v.emplace_back("string", 42);
}
在这种情况下可以正常工作,但它不能(我明白为什么)。给 bla
一个构造函数解决了这个问题,但消除了类型的聚合性,这可能会产生深远的影响。
这是对标准的疏忽吗?还是我错过了某些情况,这些情况会在我脸上爆炸,或者它没有我想的那么有用?
23.2.1/15.5
T is EmplaceConstructible into X from args, for zero or more arguments
args, means that the following expression is well-formed:
allocator_traits<A>::construct(m, p, args)
23.2.1/15
[Note: A container calls allocator_traits<A>::construct(m, p, args)
to construct an element at p using args. The default construct in std::allocator
will call ::new((void*)p) T(args)
, but specialized allocators may choose a different definition. —end note ]
因此,默认分配器使用构造函数,更改此行为可能会导致向后兼容性丢失。您可以在这个答案 .
中阅读更多内容
还有一个关于它的未来的问题"Towards more perfect forwarding" and some random discussion。
Is this an oversight in the Standard?
它被认为是标准中的缺陷,跟踪为 LWG #2089,已由 C++20 解决。在那里,构造函数语法可以对聚合类型执行聚合初始化,只要提供的表达式不会调用 copy/move/default 构造函数。由于所有形式的间接初始化(push_back
、in_place
、make_*
等)都显式使用构造函数语法,因此它们现在可以初始化聚合。
C++20 之前的版本,一个好的解决方案是难以捉摸的。
根本的问题在于你不能随意使用braced-init-lists。使用构造函数的类型的列表初始化实际上可以隐藏个构造函数,这样某些构造函数就无法通过列表初始化来调用。这就是 vector<int> v{1, 2};
问题。这将创建一个 2 元素 vector
,而不是一个只有 2 个元素的 1 元素向量。
因此,您不能在 allocator::construct
.
等通用上下文中使用列表初始化
这将我们带到:
I would think there's be a SFINAE trick to do that if possible, else resort to brace init that also works for aggregates.
这需要使用 C++17 的 is_aggregate
类型特征。但这有一个问题:你必须将这个 SFINAE 技巧传播到 all 使用间接初始化的地方。这包括 any/variant/optional
的 in_place
构造函数和安置、make_shared/unique
调用等,其中 none 使用 allocator::construct
.
并且这不包括需要此类间接初始化的用户代码。如果用户不进行与 C++ 标准库相同的初始化,人们会很不高兴。
这是一个棘手的问题,需要以不将间接初始化 API 分为允许聚合的组和不允许聚合的组的方式来解决。有many possible solutions个,其中none个比较理想
语言解决方案是最好的。
好像是this code:
#include <string>
#include <vector>
struct bla
{
std::string a;
int b;
};
int main()
{
std::vector<bla> v;
v.emplace_back("string", 42);
}
在这种情况下可以正常工作,但它不能(我明白为什么)。给 bla
一个构造函数解决了这个问题,但消除了类型的聚合性,这可能会产生深远的影响。
这是对标准的疏忽吗?还是我错过了某些情况,这些情况会在我脸上爆炸,或者它没有我想的那么有用?
23.2.1/15.5
T is EmplaceConstructible into X from args, for zero or more arguments args, means that the following expression is well-formed:
allocator_traits<A>::construct(m, p, args)
23.2.1/15
[Note: A container calls
allocator_traits<A>::construct(m, p, args)
to construct an element at p using args. The default construct instd::allocator
will call::new((void*)p) T(args)
, but specialized allocators may choose a different definition. —end note ]
因此,默认分配器使用构造函数,更改此行为可能会导致向后兼容性丢失。您可以在这个答案 .
中阅读更多内容还有一个关于它的未来的问题"Towards more perfect forwarding" and some random discussion。
Is this an oversight in the Standard?
它被认为是标准中的缺陷,跟踪为 LWG #2089,已由 C++20 解决。在那里,构造函数语法可以对聚合类型执行聚合初始化,只要提供的表达式不会调用 copy/move/default 构造函数。由于所有形式的间接初始化(push_back
、in_place
、make_*
等)都显式使用构造函数语法,因此它们现在可以初始化聚合。
C++20 之前的版本,一个好的解决方案是难以捉摸的。
根本的问题在于你不能随意使用braced-init-lists。使用构造函数的类型的列表初始化实际上可以隐藏个构造函数,这样某些构造函数就无法通过列表初始化来调用。这就是 vector<int> v{1, 2};
问题。这将创建一个 2 元素 vector
,而不是一个只有 2 个元素的 1 元素向量。
因此,您不能在 allocator::construct
.
这将我们带到:
I would think there's be a SFINAE trick to do that if possible, else resort to brace init that also works for aggregates.
这需要使用 C++17 的 is_aggregate
类型特征。但这有一个问题:你必须将这个 SFINAE 技巧传播到 all 使用间接初始化的地方。这包括 any/variant/optional
的 in_place
构造函数和安置、make_shared/unique
调用等,其中 none 使用 allocator::construct
.
并且这不包括需要此类间接初始化的用户代码。如果用户不进行与 C++ 标准库相同的初始化,人们会很不高兴。
这是一个棘手的问题,需要以不将间接初始化 API 分为允许聚合的组和不允许聚合的组的方式来解决。有many possible solutions个,其中none个比较理想
语言解决方案是最好的。