为什么大括号中的标量不被解释为 initializer_list

Why scalar in braces are not interpeted as initializer_list

考虑以下代码片段:

#include <iostream>
#include <initializer_list>

struct C
{
    C(std::initializer_list<int>) { std::cout << "list\n"; }
    C(std::initializer_list<int>, std::initializer_list<int>) { std::cout << "twice-list\n"; }
};

int main()
{
    C c1 { {1,2}, {3} }; // twice-list ctor
    C c2 { {1}, {2} }; // why not twice-list ?
    return 0;
}

Live 演示。

为什么 c2 变量的大括号中的标量值不被解释为单独的 std::initializer_list?

C c2 { {1}, {2} };

这一行没有传入两个参数std::initializer_list<int>,而是传入一个std::initializer_list<std::initializer_list<int> >。一个解决方案是像这样实例化 c2

C c2({1}, {2});

首先,有一点非常重要:您有两种不同的构造函数。特别是第一个 C(std::initializer_list<int>),称为 initializer-list 构造函数。第二个只是一个普通的用户定义构造函数。

[dcl.init.list]/p2

A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments (8.3.6).

在包含一个或多个 初始化器子句 的列表初始化中,初始化器列表构造函数在任何其他构造函数之前被考虑。也就是说,初始化列表构造函数最初是重载决策期间的唯一候选对象。

[over.match.list]/p1

When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

因此,对于 c1c2 的声明,候选集仅包含 C(std::initializer_list<int>) 构造函数。

选择构造函数后,对参数求值以查看是否存在将它们转换为参数类型的隐式转换序列。这使我们了解了初始化列表转换的规则:

[over.ics.list]/p4(强调我的):

Otherwise, if the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion.

这意味着如果初始化列表的每个元素都可以转换为 int

现在让我们关注c1:对于初始化列表{{1, 2}, {3}},初始化子句{3}可以转换为int([over.ics.list]/p9.1),但不是 {1, 2}(即 int i = {1,2} 格式错误)。这意味着违反了上述报价的条件。由于没有转换,重载决议失败,因为没有其他可行的构造函数,我们被带回到 [over.match.list]/p1:

的第二阶段
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

请注意最后的措辞变化。第二阶段的参数列表不再是单个初始化列表,而是声明中使用的花括号初始化列表的参数。这意味着我们可以根据初始化列表单独而不是同时评估隐式转换。

在initializer-list {1, 2}中,两个initializer-clause都可以转为int,所以整个initializer-clause可以转为initializer_list<int>,同理{3}。然后通过选择第二个构造函数来解决重载问题。

现在让我们关注 c2,现在应该很容易了。首先评估初始化列表构造函数,并且使用 { {1}, {2} } 肯定存在从 {1}{2}int 的转换,因此选择第一个构造函数。