函数调用中的临时构造被解释为声明

8

我最近遇到了一个问题,对我来说有些困惑。这个问题基于将临时变量的构造解释为单个构造函数参数的声明。请看下面的最小示例。

#include <iostream>

class Foo0{
public:
  Foo0(int a){};
  void doStuff() {std::cout<<"maap"<<std::endl;};
};

class Foo1{
public:
  Foo1(int a){};
  void doStuff() {std::cout<<"maap"<<std::endl;};
};

class Foo2{
public:
  Foo2(int a){};
  void doStuff() {std::cout<<"maap"<<std::endl;};
};

class Bar{
public:
  Bar(Foo0 foo0, Foo1 foo1, Foo2 foo2){};
};

int main () {
  int x = 1;

  Bar bar0(Foo0(x), Foo1(x), Foo2(x)); // Does not work: conflicting declaration ‘Foo1 x’ previous declaration as ‘Foo0 x’; conflicting declaration ‘Foo2 x’ previous declaration as ‘Foo0 x’
  Bar bar1(Foo0{x}, Foo1(x), Foo2(x)); // Works WTF
  Bar bar2(Foo0(x), Foo1{x}, Foo2(x)); // Works WTF
  Bar bar3(Foo0(x), Foo1(x), Foo2{x}); // Does not work: conflicting declaration ‘Foo1 x’ previous declaration as ‘Foo0 x’
  Bar bar4(Foo0{x}, Foo1{x}, Foo2{x}); // Works totally makes sens to me

  x.doStuff(); //Dose not work. This makes sens to me. But in the context its curious though.
}

I already read that expressions like:

Foo(a);

如果有标准构造函数,则会将解释为a的声明。这是有意义的,也是完全可以接受的,因为您可以使用{}括号使构建明确。但我不理解的是:
1. 为什么bar0的构建存在问题? 所有Foo都没有标准构造函数。因此,将类似于Foo0(x)的东西解释为对x的声明是没有意义的。
2. 为什么bar1和bar2的构建有效? 我认为bar4的构建是显而易见的,因为我对所有临时Foo使用{}括号,因此我明确说明了我想要的内容。
3. 如果只需要对其中一个Foo使用大括号以解决问题…为什么bar3的构建失败?
4. 此外,在构建任何Bar之前都声明了x。为什么编译器不会抱怨呢?
最后一个问题与我示例代码的最后一行有关。长话短说:编译器认为我想让他做什么,我在哪里遗漏了阴影的出现?
PS: 如果感兴趣--我使用gcc-4.9.2。 PPS: 我尝试使用bar的构造函数将三个Foo0作为参数。同样的故事。但错误并未提到冲突声明,而是关于重新定义x。

1
只是让你知道,bar0是一个接受三个Foo并返回一个Foo的函数声明,它本身不是一个Foo对象。此外,bar3的声明无法工作是由于您使用的编译器中存在错误。该行应该像在clang中一样工作。 - David G
@0x499602D2 在我的 g++ 4.8.1 中出现了错误。 - M.M
仅为了正确起见:bar0 是一个接受三个 Foo 并返回一个 Bar 的函数声明。 - sedriel
2个回答

10

规则是:如果一个声明具有函数声明的语法,则它就是一个函数声明;否则它就是一个变量声明。有时候,这种情况会出现令人惊讶的例子,被称为

Bar bar0(Foo0(x), Foo1(x), Foo2(x)); // Does not work: conflicting declaration ‘Foo1 x’ previous declaration as ‘Foo0 x’; conflicting declaration ‘Foo2 x’ previous declaration as ‘Foo0 x’
这是一个函数声明: bar0 是函数名称,Bar 是返回类型,参数类型分别为 Foo0Foo1Foo2。参数名都是x,这是不合法的——函数参数的名称必须不同。如果你将x x x 改成 x y z,错误就会消失。
Bar bar1(Foo0{x}, Foo1(x), Foo2(x)); // Works WTF
Bar bar2(Foo0(x), Foo1{x}, Foo2(x)); // Works WTF
Bar bar4(Foo0{x}, Foo1{x}, Foo2{x}); // Works totally makes sens to me

这些代码行创建了类型为Bar的对象bar1bar2bar4。它们不能被解析为函数声明,因为{}符号在函数声明中不是有效的语法。

因此,Foo0{x}等都是表达式,为Bar的构造函数提供参数。 Foo0{x}Foo0(x)是声明临时变量Foo0类型的等效方式,其初始化程序为x

Bar bar3(Foo0(x), Foo1(x), Foo2{x}); // Does not work: conflicting declaration ‘Foo1 x’ previous declaration as ‘Foo0 x’

我认为这是编译器的一个错误;Foo2 {x}这部分意味着这行代码不可能是函数声明;而它看起来像是一个有效的变量bar3的声明。

x.doStuff(); //Dose not work. This makes sens to me. But in the context its curious

x 是一个 int 类型;它不具有任何方法。


bar0怎么成为合法的函数声明?在那个上下文中type(name)是什么意思?它只是绑定/分组,就像int(*foo)--所以type(name)type name是一样的吗?有时我真的很喜欢C++。 - JasonN
请查看@JasonN在此处的解释,了解这个声明如何调用最令人困惑的解析。 - M.M
感谢。复杂解析的神奇之处。-Wvexing-parse很有趣。同时也有点悲哀。 - JasonN

2

1)在此实例中,Foo0(x)被视为函数bar0的一个参数。在这里,它是否有标准构造函数并不重要。这不是一个局部变量声明和初始化,而只是一个函数声明中的参数声明。

2)我猜这与解析有关,但如果我错了,请有人纠正我。示例bar1和bar2有效,因为编译器知道bar1和bar2是局部变量声明(而不是函数声明),只要它看到第一次出现{}。这些第一次出现的{}出现在x作为函数参数声明两次之前。

3)bar3的构建失败,因为编译器首先假设bar3是函数声明。函数声明需要三个参数,它们都被命名为x。显然,这是不正确的。

4)函数声明中的x只是参数的名称。它处于与之前声明的整数x不同的作用域中。


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