标准库中的初始化列表构造函数

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规范?如果我们定义了带有std :: initializer_list的构造函数,它将生成模糊的编译错误。这种构造方式的规则是什么,为什么它对具有数字作为模板参数的std :: initializer_list如此特定?

已回滚到之前的版本。@cse,在答案发表后就不能完全更改... - Barry
@Barry,现在有一个答案回答了第一个版本,另一个答案回答了第二个版本(完全透明:是我写的)。在这种情况下,最好插入一个粗体注释,说明“编辑:”或类似的内容。我甚至没有意识到这个编辑,未来的用户也不会意识到回滚,除非他们读评论。编辑:刚刚注意到不是原作者编辑了问题。不确定...但也许回滚是合适的。 - Johannes Schaub - litb
2个回答

11
如果一个类有初始化列表构造函数,那么{无论放什么}的意思是将{无论放什么}作为参数传递给当前的构造函数(如果没有初始化列表构造函数,则将无论放什么作为参数传递)。所以让我们简化设置并忽略其他构造函数,因为显然编译器不关心它们。
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>},这就是出现歧义的原因,因为最佳转换序列组不包含单个候选项,而是两个。


8
{}转换为标量类型(如intdoublechar*等)是标识转换。
{}转换为除std::initializer_list特化之外的类类型(例如std::string)是用户定义的转换。
前者优于后者。

但是为什么 A a1({}); 是模棱两可的?我正在尝试找到直接初始化和直接列表初始化在这里的区别,但还没有成功。 - Daniel Langr
1
@DanielLangr,我修改了我的答案以解释其中的区别。 - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接