// Example program
#include <iostream>
#include <string>
#include <utility>
class A {
public:
int x;
};
class B {
public:
B(A &&a) : m_a(std::move(a)) {}
A m_a;
};
int main()
{
B var(std::move(A()));
// B var(A()); // does not compile why?
std::cout << var.m_a.x << "\n";
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码片段中,注释掉的行不会编译.出现错误消息,它将var视为函数声明.即使A具有构造函数的参数,它仍然被视为函数声明.有没有办法写它,所以它不会被视为函数声明?在这种情况下,使用typename没有帮助.
这个问题被称为最令人烦恼的解析,它准确地描述了你的情况,解析器不知道你是想要一个函数声明,还是一个对象的实例化.这是在这个意义上,给予人类,一些代码给一块呢暧昧X,但是编译器它显然做ÿ.
解决它的一种方法是使用列表初始化语法:
B var{A()}; // note the use of brackets
Run Code Online (Sandbox Code Playgroud)
这不再是模棱两可的,它会根据需要调用构造函数.您也可以A在以下两个中使用它:
B var(A{});
B var{A{}};
Run Code Online (Sandbox Code Playgroud)
现在,为什么它含糊不清?举例来说,函数参数的声明是函数的指针:
int foo(int (*bar)());
Run Code Online (Sandbox Code Playgroud)
这里,参数是函数的指针类型,它不带参数,返回类型为int.声明函数指针的另一种方法是省略声明符中的括号:
int foo(int bar());
Run Code Online (Sandbox Code Playgroud)
其中仍然声明一个与前一个函数相同的指针.由于我们处于声明参数(parameter-declaration-clause)的上下文中,所解析的语法是一个type-id,它部分构建在abstract-declarator之上.因此,这允许我们删除标识符:
int foo(int());
Run Code Online (Sandbox Code Playgroud)
而且我们最终仍然使用相同的类型.
尽管如此,让我们检查一下你的代码并将它与上面的例子进行比较:
B var(A());
Run Code Online (Sandbox Code Playgroud)
我们有一些东西看起来像一个B初始化类型的变量声明A().到现在为止还挺好.但是等等,你说这不编译!
出现错误消息,它将var视为函数声明.
var实际上是一个函数声明,即使对你而言,它首先看起来并不像那样.此行为是由于[dcl.ambig.res]/1:
[......]决议是考虑任何可能是声明声明的构造.
这句话适用于此.回顾前面的例子:
int foo(int());
Run Code Online (Sandbox Code Playgroud)
这和你的代码一样模棱两可:foo可能是一个声明,因此解决方案是将其解释为一个.你B var(A())可能也是一个声明,所以它拥有相同的分辨率.
该标准在[dcl.ambig.res]/1,示例#1中有一些这些案例的例子,并且还提供了关于如何在[dcl.ambig.res]/1上消除歧义的一些提示,注意#1:
[注意:通过在参数周围添加括号,可以明确地消除声明的歧义.通过使用复制初始化或列表初始化语法,或通过使用非函数样式转换,可以避免歧义. - 结束说明]
[例如:
Run Code Online (Sandbox Code Playgroud)struct S { S(int); }; void foo(double a) { S w(int(a)); // function declaration S x(int()); // function declaration S y((int(a))); // object declaration S y((int)a); // object declaration S z = int(a); // object declaration }- 结束例子]