为什么 ctor 中的 std::initializer_list 没有按预期运行?

xml*_*lmx 14 c++ initializer-list c++11 c++17 ctad

#include <vector>

int main()
{
    auto v = std::vector{std::vector<int>{}};
    return v.front().empty(); // error
}
Run Code Online (Sandbox Code Playgroud)

在线演示

然而,根据 Scott Meyers 的Effective Modern C++(强调原文):

但是,如果一个或多个构造函数声明了一个类型为 的参数 std::initializer_list,则使用花括号初始化语法的调用强烈倾向于采用 的重载std::initializer_lists强烈。如果编译器有任何方法可以将使用花括号初始化器的调用解释为采用 a 的构造函数std::initializer_list,则编译器将采用该解释。

所以,我认为std::vector{std::vector<int>{}};应该产生一个对象std::vector<std::vector<int>>而不是std::vector<int>.

谁错了?为什么?

Dav*_*ing 7

Meyers 大部分是正确的(例外是T{}如果存在默认构造函数,则值初始化),但他的陈述是关于重载决议的。这发生在 CTAD 之后,CTAD 选择要使用的(以及构造函数集)。

CTAD 不“偏爱”初始化列表构造函数,因为它更喜欢复制而不是包装可嵌套模板,例如std::vectorstd::optional。(可以用推导指南覆盖它,但标准库使用默认值,正如人们所期望的那样。)这在一定程度上可以防止创建奇怪的类型,例如std::optional<std::optional<int>>,但它使泛型代码更难编写,因为它给出了

template<class T> void f(T x) {
  std::vector v{x};
  // …
}
Run Code Online (Sandbox Code Playgroud)

一种以不规则非注入方式取决于其论点类型的含义。特别是,v可能std::vector<int>T=int或与T= std::vector<int>,尽管是std::vector<std::deque<int>>if T= std::deque<int>。不幸的是,基于其他一些类型计算一种类型的工具在通用上下文中不可用。


jpo*_*o38 5

auto v = std::vector{std::vector<int>{}};实际上创建 astd::vector<int>因为它使用std::vector复制构造函数。它被编译器解释为:

auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );
Run Code Online (Sandbox Code Playgroud)

所以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>{} };
Run Code Online (Sandbox Code Playgroud)