为什么 Foo({}) 调用的是 Foo(0) 而不是 Foo()?

15

从代码中生成的可执行文件由clang 3.5.0和gcc 4.9.1生成。

#include <iostream>

struct Foo
{
   Foo() { std::cout << "Foo()" << std::endl; }
   Foo(int x) { std::cout << "Foo(int = " << x << ")" << std::endl; }
   Foo(int x, int y) { std::cout << "Foo(int = " << x << ", int = " << y << ")" << std::endl; }
};

int main()                 // Output
{                          // ---------------------
   auto a = Foo();         // Foo()
   auto b = Foo(1);        // Foo(int = 1)
   auto c = Foo(2, 3);     // Foo(int = 2, int = 3)
   auto d = Foo{};         // Foo()
   auto e = Foo{1};        // Foo(int = 1)
   auto f = Foo{2, 3};     // Foo(int = 2, int = 3)
   auto g = Foo({});       // Foo(int = 0)          <<< Why?
   auto h = Foo({1});      // Foo(int = 1)
   auto i = Foo({2, 3});   // Foo(int = 2, int = 3)
}

按照注释的方式进行操作。

来自cppreference: cpp/language/list initialization

[...]

T( { arg1, arg2, ... } )    (7)

[...]

The effects of list initialization of an object of type T are:

If T is an aggregate type, aggregate initialization is performed.

Otherwise, If the braced-init-list is empty and T is a class type with a default constructor, value-initialization is performed.

[...]

我得出结论,Foo({})应该调用默认构造函数。

哪里有问题?


4
这个错误在cppreference网站中。 - Casey
2
将cppreference修正为“从非括号大括号初始化列表初始化类型为T的对象的效果为...”,这样会更清晰吗? - Casey
2
也许用 U({...}) 替换 T({...}) 是个好主意,这样可以避免文本引用 T 的问题,而这里是不正确的。 - Johannes Schaub - litb
1
@Casey 和 litb,我恢复并重新排列了list-initialization 的要点,并使措辞更具体化,说明了正在初始化的内容。 - Cubbi
1
@precarious 这仍然是精确的。如果移除 int 构造函数,你将会调用复制/移动构造函数。 - Johannes Schaub - litb
显示剩余14条评论
1个回答

18
默认构造函数仅适用于使用一对花括号的情况:
auto a = Foo();         // Foo()
auto b = Foo{};         // Foo()

Foo({}) 只会调用以空列表作为参数的构造函数,通过复制列表初始化选择任何构造函数的参数。[dcl.init]/16:

如果目标类型是(可能带有cv限定符的)类类型:
— 如果初始化是直接初始化[…],则考虑构造函数。逐个枚举适用的构造函数(13.3.1.3),并通过重载决议(13.3)选择最佳构造函数。所选的构造函数被调用来初始化该对象,其参数为初始化表达式或表达式列表如果没有构造函数适用,或者重载决议不明确,则初始化是非法的。

你有一个参数:空的花括号初始化列表。存在一个列表初始化序列将 {} 转换为 int,因此选择构造函数 Foo(int) 进行重载决议。由于 {} 暗示了 value-intialization,对于标量类型来说意味着 zero-initialization,因此参数被初始化为零。
cppreferences 文档中也没有错误:对于 (7),它说明:

7) 在函数式转换表达式或其他直接初始化中,使用花括号初始化列表作为构造函数参数

这显然导致与上述引用相同的结果:使用(空的)花括号初始化列表调用构造函数。

6
为了给OP解释清楚,这段代码在这种情况下等同于 auto g = Foo(int{});,而且 int{} == 0 - cdhowie
1
说到非标准方法,在Visual Studio / msvc中,当“列表”包含一个可以解释为整数类型的文字常量时,例如{42},它不是按照标准应该是一个列表,而是在编译器 - msvc - 看来是一个“真正”的整数;例如auto a = {1},如果使用msvc,则a是一个整数。 - user2485710
2
@Hurkyl 在这种情况下的预期结果是一个错误,因为 {} 实际上没有类型,因此根据标准,编译器应该将 auto 视为类型推断请求,a 作为标签,= 作为赋值/复制构造函数,{1} 作为一个没有类型但包含文字的东西;因此编译器不应该能够推断出 a 的任何类型,这就是为什么它应该生成一个错误而不是一个整数类型实例。微软对规则进行了例外处理,我认为这个例外不会使 {} 的使用更好。 - user2485710
1
初始化列表没有类型,因为它们不是表达式。但是 auto a = {1} 是良好的形式,并且不应该产生错误。类型应该被推断为 std::initializer_list<int>(这是一个备受争议的特殊情况)。如果 Microsoft 推断为 int,那就是一个错误。但你不必期望出现错误。 - gigabytes
1
根据N3922auto a = {1};应该推导为std::initializer_listauto a{1};应该推导为int,而auto a{1,2};则是不合法的。 - T.C.
显示剩余6条评论

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