在这样的代码中:
#include <iostream>
#include <initializer_list>
#include <string>
struct A
{
A() { std::cout << "2" << std::endl; }
A(int a) { std::cout << "0" << std::endl; }
A(std::initializer_list<std::string> s) { std::cout << "3" << std::endl; }
A(std::initializer_list<int> l) { std::cout << "1" << std::endl; }
};
int main()
{
A a1{{}};
}
为什么调用std::initializer_list<int>
构造函数的规范?
如果我们定义带有std::initializer_list<double>
的构造函数,它将产生歧义编译错误。这种构造的规则是什么?为什么std::initializer_list
以数字作为模板参数如此具体?
答案 0 :(得分:7)
{}
标量类型(例如int
,double
,char*
等)是标识转换。
{}
到std::initializer_list
的特殊化以外的类类型(例如,std::string
)是用户定义的转化。
前者胜过后者。
答案 1 :(得分:7)
如果一个类有一个初始化列表构造函数,那么{whatever goes here}
意味着将{whatevergoeshere}
作为参数传递给当前构造函数(如果没有初始化列表构造函数,则whatever goes here
将作为参数传递给参数)。
因此,让我们简化设置并忽略其他构造函数,因为显然编译器并不关心它们
void f(std::initializer_list<std::string> s);
void f(std::initializer_list<int> l);
对于f({{}})
,我们有此规则
否则,如果参数类型是std :: initializer_list并且初始化列表的所有元素都可以隐式转换为X,则隐式转换序列是将列表元素转换为X所需的最差转换,或者如果初始化列表没有元素,则进行标识转换。即使在调用初始化列表构造函数的上下文中,此转换也可以是用户定义的转换。
此处我们有一个元素{}
,它需要用户定义的转化来初始化std::string
而不需要int
的转化(身份)。因此,选择int
。
对于f({{{}}})
,元素为{{}}
。它可以转换为int
吗?规则是
- 如果初始化列表有一个本身不是初始化列表的元素,则隐式转换序列是将元素转换为参数类型所需的序列
- ...
- 除上述列举的所有情况外,不可进行任何转换。
可以转换为std::string
吗?是的,因为它有一个初始化列表构造函数,它具有std::initializer_list<char> init
参数。因此,此次选择std::string
。
与A a3({})
的区别在于,在这种情况下,它不是列表初始化,而是带有{}
参数的“正常”初始化(请注意,由于缺少外部大括号而少了一个嵌套) 。这里我们使用f
调用了两个{}
- 函数。由于两个列表都没有元素,因此我们都有身份转换,因此存在歧义。
在这种情况下,编译器也会考虑f(int)
并与其他两个函数联系起来。但是,如果声明int
- 参数比initializer_list
参数更糟,则会应用决胜局。所以你有一个部分订单{int} < {initializer_list<string>, initializer_list<int>}
,这是歧义的原因,因为最好的一组转换序列不包含一个候选者,而是两个。