为什么花括号初始化列表在函数调用和构造函数中的行为不同?

5
使用clang 3.5.0和gcc 4.9.1编译以下代码,在最后一个语句处会产生错误。
#include <iostream>

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

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

int main()
{
   Foo({}, {});   // Foo(int = 0, int = 0)
   Foo({1}, {2}); // Foo(int = 1, int = 2)
   Foo({1, 2});   // Foo(int = 1, int = 2)

   bar({}, {});   // bar(int = 0, int = 0)
   bar({1}, {2}); // bar(int = 1, int = 2)
   bar({1, 2});   // error: no matching function for call to 'bar'  <<< Why? <<<
}

为什么Foo({1, 2})是正确的,而bar({1, 2})不是?

特别是,了解背后的原理会很有帮助。


3
我的猜测是,Foo({1,2}) 创建一个临时的 Foo 对象并调用复制构造函数。 - Borgleader
@Borgleader 谢谢,明白了! :-) - precarious
@Borgleader 的评论是正确的 - 使用 {1, 2} 可以创建一个临时的 Foo 对象,但仅适用于期望 Foo 类型的情况下。实际上无法将多个参数传递到该函数中。 - Freddie Chopin
嗯,有趣的是 vec.insert(vec.back(), {...}) 可以很好地工作,与您的代码相反(调用了 std::initializer_list 版本)。我刚才写了这个代码,希望 C++ 委员会在实现初始化列表时已经想到了这一点,因此这将 "只需要工作",而且确实如此。这真是太棒了。 - Damon
2个回答

6

Foo({1,2})创建了一个临时的Foo对象并调用复制构造函数。

请查看带有删除复制构造函数的修改后示例:http://coliru.stacked-crooked.com/a/6cb80746a8479799

它会产生错误:

main.cpp:6:5: note: candidate constructor has been explicitly deleted
    Foo(const Foo& f) = delete;

但在原始示例中,不是隐式生成了一个复制构造函数吗?为什么要删除它并导致另一个无法编译的原因?这并没有解释为什么无法调用 bar;而且这完全与 bar 的参数有关,请参见修复编译错误的方法 - Lorah Attkins
@NorahAttkins 你误解了重点。重点是,初始化列表没有将这两个整数传递给构造函数(这是 OP 假设的,并且想知道为什么在一个也需要两个整数的函数调用中不起作用),而是生成了一个临时对象并调用了复制构造函数。删除它后显示调用了复制构造函数。 - Borgleader
没有,它没有。看看这个例子。如果我删除对Foo({1, 2})的调用,当调用bar时程序仍然无法编译,而从未提到删除复制构造函数的任何内容。对bar的调用从未创建过临时的Foo对象。 - Lorah Attkins
@NorahAttkins 请重新阅读我所说的,OP的合理假设是初始化列表与接受两个整数的签名兼容,但实际上在Foo对象的情况下它创建了一个临时对象,这意味着他不应该期望调用bar成功。为什么调用bar失败是另一回事 - Borgleader
是的,你说得对;我太傻了。感谢你在“initializer_list”类型上给我上课……嗯,我应该说“没有类型”。 - Lorah Attkins

1

这行代码

bar({1, 2});

实际上传递给bar函数的是一个临时对象。
<brace-enclosed initializer list> // it's made clear in the comments that brace initializers have no type

无法将这个临时对象转换为第一个参数的类型,第一个参数是一个 int。因此出现了 error

无法将 <brace-enclosed initializer list> 转换为参数 1int 类型。

void bar(int, int)

不,那是不正确的。花括号初始化列表没有类型。 - Columbo
1
@NorahAttkins 【花括号初始化程序没有类型】(http://www.youtube.com/watch?v=wQxj20X-tIU&list=UUMlGfpWw-RUdWX_JbLCukXg#t=1792) - Borgleader
1
是的。您所展示的结果源于一种特殊(愚蠢)的规则,正是为了这种情况而引入的。 - Columbo
@Borgleader 这有点令人困惑。由于类型不匹配,调用 bar 时程序是否仍然失败?(我很快会观看视频)据我理解,“没有类型”类似于“lambda没有类型”,这意味着它们具有一种我们不知道的类型。 - Lorah Attkins
@NorahAttkins 除了lambda有一个类型,但它是匿名的。 - Borgleader

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