αλε*_*λυτ 6 c++ initializer-list language-lawyer
请考虑以下代码段:
#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;
}
Run Code Online (Sandbox Code Playgroud)
现场演示.
为什么c2变量括号中的标量值不被解释为单独的std :: initializer_list?
首先,有一点非常重要:您有两种不同类型的构造函数。特别是第一个,C(std::initializer_list<int>)称为初始化列表构造函数。第二个只是一个普通的用户定义的构造函数。
[dcl.init.list]/p2
如果构造函数的第一个参数的类型
std::initializer_list<E>或引用可能是 cv 限定std::initializer_list<E>的某种类型E,并且要么没有其他参数,要么所有其他参数都有默认参数,则构造函数是初始值设定项列表构造函数(8.3.6)。
在包含一个或多个初始值设定项子句的列表初始化中,初始值设定项列表构造函数在任何其他构造函数之前被考虑。也就是说,初始化列表构造函数最初是重载决策期间唯一的候选者。
[over.match.list]/p1
当非聚合类类型的对象
T被列表初始化时,8.5.4 指定根据本节中的规则执行重载决策,重载决策分两个阶段选择构造函数:
最初,候选函数是类的初始值设定项列表构造函数 (8.5.4)
T,参数列表由初始值设定项列表作为单个参数组成。如果没有找到可行的初始化列表构造函数,则再次执行重载决策,其中候选函数是类的所有构造函数
T,参数列表由初始化列表的元素组成。
因此,对于 和 的声明c1,c2候选集仅由C(std::initializer_list<int>)构造函数组成。
选择构造函数后,将评估参数以查看是否存在将它们转换为参数类型的隐式转换序列。这将我们带到初始化列表转换的规则:
[over.ics.list]/p4(强调我的):
否则,如果参数类型为
std::initializer_list<X>且初始值设定项列表的所有元素都可以隐式转换为X,则隐式转换序列是将列表的元素转换为 所需的最差转换X,或者如果初始值设定项列表没有元素,则标识转换。
这意味着如果初始值设定项列表的每个元素都可以转换为 ,则存在转换int。
c1现在让我们关注:对于initializer-list {{1, 2}, {3}},initializer-clause{3}可以转换为int([over.ics.list]/p9.1),但不能{1, 2}(即int i = {1,2}格式不正确)。这意味着上述报价的条件被违反。由于没有转换,重载解析失败,因为没有其他可行的构造函数,我们被带回到 [over.match.list]/p1 的第二阶段:
- 如果没有找到可行的初始化列表构造函数,则再次执行重载决策,其中候选函数是类的所有构造函数
T,参数列表由初始化列表的元素组成。
注意最后的措辞变化。第二阶段的参数列表不再是单个初始化列表,而是声明中使用的花括号初始化列表的参数。这意味着我们可以根据初始化列表单独评估隐式转换,而不是同时评估。
在initializer-list 中{1, 2},两个initializer-clause 都可以转换为int,因此整个initializer-clause 可以转换为initializer_list<int>,对于 也是如此{3}。然后通过选择第二个构造函数来解决重载解析。
现在让我们关注c2,现在应该很容易了。首先评估初始化列表构造函数,并且使用肯定存在到from和 的{ {1}, {2} }转换,因此选择第一个构造函数。int{1}{2}