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

Why does std::initializer_list in ctor not behave as expected?

#include <vector>

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

online demo

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

If, however, one or more constructors declare a parameter of type std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking a std::initializer_list, compilers will employ that interpretation.

所以,我认为 std::vector{std::vector<int>{}}; 应该生成 std::vector<std::vector<int>> 而不是 std::vector<int> 的对象。

谁错了?为什么?

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

auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );

所以 v 最终成为 std::vector<int>,而不是 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>{} };

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

CTAD 并不“更喜欢”初始化列表构造函数,因为它更喜欢 复制 而不是包装 std::vectorstd::optional 等可嵌套模板。 (可以用演绎指南覆盖它,但标准库使用默认值,正如人们可能期望的那样。)这在某种程度上是有道理的,因为它可以防止创建像 std::optional<std::optional<int>> 这样的奇怪类型,但它会使通用代码更难编写因为它给

template<class T> void f(T x) {
  std::vector v{x};
  // …
}

一个取决于其参数类型的含义,采用 不规则 非单射 方式。特别是,v 可能是 std::vector<int>T=intT=std::vector<int>,尽管 std::vector<std::deque<int>> 如果 T=std::deque<int>。不幸的是,基于其他一些类型计算一种类型的工具不能在通用上下文中使用。