为什么大括号中的标量没有作为initializer_list插入



请考虑以下代码片段:

#include <iostream>
#include <initializer_list>
struct C
{
    C(std::initializer_list<int>) { std::cout << "listn"; }
    C(std::initializer_list<int>, std::initializer_list<int>) { std::cout << "twice-listn"; }
};
int main()
{
    C c1 { {1,2}, {3} }; // twice-list ctor
    C c2 { {1}, {2} }; // why not twice-list ?
    return 0;
}

现场演示。

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

首先,非常重要的事情:您有两种不同类型的构造函数。第一个特别的C(std::initializer_list<int>)称为初始值设定项列表构造函数。第二个只是一个普通的用户定义构造函数。

[dcl.init.list]/p2

构造函数是初始值设定项列表构造函数,如果它的第一个参数是类型 std::initializer_list<E> 或引用某些类型 E 的可能符合 cv 的std::initializer_list<E>,并且没有其他参数,或者所有其他参数都有默认参数 (8.3.6(。

在包含一个或多个初始值设定项子句的列表初始化中,初始值设定项列表构造函数先于任何其他构造函数进行考虑。也就是说,初始值设定项列表构造函数最初是重载解析期间的唯一候选项。

[over.match.list]/p1

当非聚合类类型 T 的对象进行列表初始化时,8.5.4 指定根据本节中的规则执行重载解析,重载解析将分两个阶段选择构造函数:

  • 最初,候选函数是类T的初始值设定项列表构造函数 (8.5.4(,参数列表由作为单个参数的初始值设定项列表组成。

  • 如果未找到可行的初始值设定项列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由元素组成 初始值设定项列表。

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

选择构造函数后,将计算参数,以查看是否存在将其转换为参数类型的隐式转换序列。这将我们带到初始值设定项列表转换的规则:

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

否则,如果参数类型std::initializer_list<X>并且初始值设定项列表的所有元素都可以隐式转换为 X ,则隐式转换序列是转换 元素X,或者如果初始值设定项列表没有元素,则进行标识转换。

这意味着如果初始值设定项列表的每个元素都可以转换为 int ,则存在转换。

现在让我们关注c1:对于初始值设定项列表{{1, 2}, {3}},初始值设定项子句{3}可以转换为int([over.ics.list]/p9.1(,但不能转换为{1, 2}(即int i = {1,2}格式不正确(。这意味着违反了上述报价的条件。由于没有转换,重载解析失败,因为没有其他可行的构造函数,我们被带回 [over.match.list]/p1 的第二阶段:

  • 如果未找到可行的初始值设定项列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由元素组成 初始值设定项列表。

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

在初始值设定项列表{1, 2}中,两个初始值设定项子句都可以转换为int,因此整个初始值设定项子句可以转换为initializer_list<int>{3}也是如此。然后,通过选择第二个构造函数来解决重载解析。

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

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

这条线不是在两个std::initializer_list<int>参数中传递,而是在一个std::initializer_list<std::initializer_list<int> >中传递。一个解决方案是像这样实例化c2

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

最新更新