14

在这样的代码中:

#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用数字作为模板参数如此具体?

4

2 回答 2

10

如果一个类有一个初始化列表构造函数,则{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-parameter 比参数更差initializer_list。所以你有一个偏序{int} < {initializer_list<string>, initializer_list<int>},这是产生歧义的原因,因为最好的转换序列组不包含一个候选者,而是两个。

于 2018-01-29T14:04:09.700 回答
8

{}到标量类型(例如int, double,char*等)是身份转换。

{}std::initializer_list到除特化(例如)以外的类类型std::string是用户定义的转换。

前者击败后者。

于 2018-01-29T11:29:12.230 回答