struct X
{
X() { std::cout << "default ctor" << std::endl; }
};
int main()
{
X({});
}
打印出来
default ctor
这是有道理的,因为空括号值初始化对象(我认为)。 然而,
struct X
{
X() { std::cout << "default ctor" << std::endl; }
X(std::initializer_list<int>) { std::cout << "list initialization" << std::endl; }
};
int main()
{
X({});
}
为此,我得到了
initializer list
我没有发现这种行为如此奇怪,但我并不完全相信。对此有什么规定?
这种行为是否写在标准的某些部分?
答案 0 :(得分:10)
要查看实际情况,请声明复制和移动构造函数,在C ++ 14模式或更早版本中进行编译,并禁用复制省略。
输出:
default ctor
move ctor
在第一个片段中,编译器查找带有单个参数的X
构造函数,因为您提供了一个参数。这些是复制和移动构造函数X::X(const X&)
和X::X(X&&)
,如果您不自己声明它们,编译器将隐式声明这些构造函数。然后,编译器使用默认构造函数将{}
转换为X
对象,并将该X
对象传递给移动构造函数。 (您必须使用fno-elide-constructors
来查看此内容,否则编译器将忽略此移动,并且在C ++ 17中,复制省略变为强制性。)
在第二个代码段中,编译器现在可以选择将{}
转换为X
(然后调用移动构造函数),或将{}
转换为std::initializer_list<int>
(然后调用初始化列表构造函数)。根据[over.ics.list] /6.2,调用默认构造函数的{}
到X
的转换是用户定义的转换,而根据[over.ics.list] / 4,从{}
到std::initializer_list<int>
的转换是身份转换。标识转换优于用户定义的转换,因此编译器调用初始化列表构造函数。
答案 1 :(得分:7)
这种行为是否写在标准的某些部分?
当然。这完全由[dcl.init]/16中的规则决定,强调我的匹配你的初始值:
初始化器的语义如下。目的地类型是 正在初始化的对象或引用的类型以及源 type是初始化表达式的类型。如果初始化程序是 不是单个(可能带括号的)表达式,源类型是 没有定义。
如果初始化程序是(非括号)braced-init-list,则对象或引用是列表初始化的([dcl.init.list])。
[...]
如果目标类型是(可能是cv限定的)类类型:
- 如果初始化是直接初始化,或者它是复制初始化,其中cv-nonqualified版本的源 type与类的类相同,或者是类的派生类 目的地,构造函数被视为。适用的构造函数 枚举([over.match.ctor]),并选择最好的一个 重载决议([over.match])。这样选择的构造函数是 调用初始化对象,使用初始化表达式或 表达式列表作为其参数。如果没有构造函数适用,或者 重载分辨率不明确,初始化是不正确的。
- [...]
您提供带括号的空brace-init-list,因此只应用后一个项目符号。考虑构造函数,在第一种情况下,我们最终从默认的初始化X
进行复制初始化。在后一种情况下,选择initializer_list
c'tor作为更好的匹配。选择此重载的规则在[over.ics.list]:
当参数是初始化列表([dcl.init.list])时,它不是 表达式和特殊规则适用于将其转换为参数 类型。
如果参数类型是std :: initializer_list或“X的数组”和 可以隐式转换初始化列表的所有元素 对于X,隐式转换序列是最差的转换 将列表元素转换为X所必需的。此转换可以 即使在调用的上下文中,也是用户定义的转换 初始化列表构造函数。
否则,如果参数是非聚合类X并且过载 每[over.match.list]的分辨率选择一个最好的构造函数 X从中执行X类型对象的初始化 参数初始化列表,隐式转换序列是一个 用户定义的转换序列。如果多个构造函数是可行的 但没有一个比其他更好,隐式转换序列 是模糊的转换序列。用户定义的转换是 允许将初始化列表元素转换为 构造函数参数类型,除了[over.best.ics]中所述。