请考虑以下代码段:
#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;
}
here演示。
为什么c2
变量的大括号中的标量值不会被解释为单独的std :: initializer_list?
答案 0 :(得分:4)
首先,非常重要的事情:你有两种不同的构造函数。第一个特别是C(std::initializer_list<int>)
,称为初始化列表构造函数。第二个是普通的用户定义构造函数。
[dcl.init.list] / P2
构造函数是初始化列表构造函数,如果其第一个参数类型为
std::initializer_list<E>
,或者对某些类型std::initializer_list<E>
可能引用cv限定E
,并且没有其他参数,或者所有其他参数都有默认参数(8.3.6)。
在包含一个或多个 initializer-clauses 的列表初始化中,初始化列表构造函数在任何其他构造函数之前被考虑。也就是说,初始化列表构造函数最初是重载解析期间的唯一候选者。
[over.match.list] / P1
当非聚合类类型
T
的对象被列表初始化,使得8.5.4指定根据本节中的规则执行重载解析时,重载决策分两个阶段选择构造函数:
最初,候选函数是类
T
的初始化列表构造函数(8.5.4),参数列表由初始化列表作为单个参数组成。如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类
T
的所有构造函数,参数列表由元素组成 初始化列表。
因此,对于c1
和c2
的声明,候选集仅包含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
的所有构造函数,参数列表由元素组成 初始化列表。
注意最后措辞的变化。第二阶段中的参数列表不再是单个初始化列表,而是声明中使用的braced-init-list的参数。这意味着我们可以根据初始化列表单独而不是同时评估隐式转换。
在initializer-list {1, 2}
中,两个initializer-clause都可以转换为int
,因此整个initializer-clause可以转换为initializer_list<int>
,{{1} }}。然后在选择第二个构造函数的情况下解决重载分辨率。
现在让我们关注{3}
,现在应该很容易。首先评估初始化列表构造函数,并且使用c2
肯定存在从{ {1}, {2} }
和int
到{1}
的转换,因此选择了第一个构造函数。
答案 1 :(得分:0)
C c2 { {1}, {2} };
这一行不传递std::initializer_list<int>
的两个参数,而是传递一个std::initializer_list<std::initializer_list<int> >
。一个解决方案是改为实例化c2
,如下所示:
C c2({1}, {2});