嵌套列表初始化如何转发其参数?

How does nested list-initialization forward its arguments?

在对向量的初始化中

  std::vector<std::pair<int, std::string>> foo{{1.0, "one"}, {2.0, "two"}};

我应该如何解释 foo 的结构?据我了解,

  1. 构造函数是用大括号初始化语法调用的,因此重载 vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() ); 是强烈推荐和选择的
  2. std::initializer_list<T>的模板参数推导为std::pair<int, std::string>
  3. foo 的每个元素都是一个 std::pair。但是,std::pair 没有接受 std::initializer_list.
  4. 的重载

我不太确定第 3 步。我知道内括号不能解释为 std::initializer_list,因为它们是异构的。标准中的什么机制实际上正在构造 foo 的每个元素?我怀疑答案与将内部大括号中的参数转发到重载 template< class U1, class U2 pair( U1&& x, U2&& y ); 有关,但我不知道这叫什么。

编辑:

我认为提出相同问题的一种更简单的方法是:什么时候

std::map<int, std::string> m = { // nested list-initialization
           {1, "a"},
           {2, {'a', 'b', 'c'} },
           {3, s1}

the cppreference example 所示,在标准中它说 {1, "a"}{2, {'a', 'b', 'c'} }{3, s1} 每个都转发给 [=15 的构造函数=]?

您混淆了两个不同的概念:

1) 初始化列表

initializer_list<T>: 主要用于集合的初始化。在这种情况下,所有成员都应该属于同一类型。 (不适用于 std::pair

示例:

std::vector<int> vec {1, 2, 3, 4, 5};

2)统一初始化

Uniform initialization:其中大括号用于构造和初始化一些对象,如结构、类(使用适当的构造函数)和基本类型(整数、字符等)。

示例:

struct X { int x; std::string s;}; 
X x{1, "Hi"}; // Not an initializer_list here.

话虽如此,要使用大括号初始化程序初始化 std::pair,您将需要一个包含两个元素的构造函数,即第一个和第二个元素,而不是 std::initializer_list<T>。例如在我安装了 VS2015 的机器上,这个构造函数看起来像这样:

template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<is_convertible<_Other1, _Ty1>::value
            && is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr pair(_Other1&& _Val1, _Other2&& _Val2) // -----> Here the constructor takes 2 input params
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Val1)), // ----> initialize the first 
            second(_STD forward<_Other2>(_Val2)) // ----> initialize the second
    {   // construct from moved values
    }

通常,表达式是由内而外分析的:内部表达式有类型,然后这些类型决定外部运算符的含义以及要调用的函数。

但是初始化列表不是表达式,也没有类型。因此,由内而外是行不通的。需要特殊的重载决议规则来说明初始化列表。

第一条规则是:如果构造函数的单个参数是initializer_list<T>,那么在第一轮重载决议中只考虑这样的构造函数(over.match.list)。

第二条规则是:对于每个 initializer_list<T> 个候选者(每个 class 可能有多个候选者,每个 T 个不同),检查每个初始化器可以转换为 T,只有那些候选人保留在计算出的地方 (over.ics.list)。

这第二个规则基本上是,初始化列表没有类型的障碍被采取并恢复由内而外的分析。

一旦重载决议决定应使用特定的 initializer_list<T> 构造函数,复制初始化用于初始化 elements of type T of the initializer 列表。