为什么大括号中的标量不被解释为 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.
因此,对于 c1
和 c2
的声明,候选集仅包含 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
的转换,因此选择第一个构造函数。
考虑以下代码片段:
#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-qualifiedstd::initializer_list<E>
for some typeE
, 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.
因此,对于 c1
和 c2
的声明,候选集仅包含 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 toX
, the implicit conversion sequence is the worst conversion necessary to convert an element of the list toX
, 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
的转换,因此选择第一个构造函数。