为什么 ctor 中的 std::initializer_list 没有按预期运行?
#include <vector>
int main()
{
auto v = std::vector{std::vector<int>{}};
return v.front().empty(); // error
}
看在线演示
然而,根据 Scott Meyers 的Effective Modern C++(强调原文):
所以,我认为std::vector{std::vector<int>{}};应该产生一个对象std::vector<std::vector<int>>而不是std::vector<int>.
谁错了?为什么?
回答
Meyers 大部分是正确的(例外是T{}如果存在默认构造函数,则值初始化),但他的陈述是关于重载决议的。这发生在 CTAD 之后,CTAD 选择要使用的类(以及构造函数集)。
CTAD 不“偏爱”初始化列表构造函数,因为它更喜欢复制而不是包装可嵌套模板,例如std::vector或std::optional。(可以用推导指南覆盖它,但标准库使用默认值,正如人们所期望的那样。)这在一定程度上可以防止创建奇怪的类型,例如std::optional<std::optional<int>>,但它使泛型代码更难编写,因为它给出了
template<class T> void f(T x) {
std::vector v{x};
// …
}
一种以不规则和非注入方式取决于其论点类型的含义。特别是,v可能std::vector<int>与T=int或与T= std::vector<int>,尽管是std::vector<std::deque<int>>if T= std::deque<int>。不幸的是,基于其他一些类型计算一种类型的工具在通用上下文中不可用。
auto v = std::vector{std::vector<int>{}};实际上创建 astd::vector<int>因为它使用std::vector复制构造函数。它被编译器解释为:
auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );
所以v最终成为 a std::vector<int>,而不是 a std::vector<std::vector<int>>。
正如“P Kramer”和“Marek P”在评论中所报告的那样,以下语法将帮助任何编译器完成您的期望:
auto v = std::vector{{std::vector<int>{}}};
auto v = std::vector<std::vector<int>>{ std::vector<int>{} };