最麻烦的解析和指针间接/取消引用



最小代码:

struct A {
  A(int = 0) {}
};
int i = 0, *p = &i;
int* foo () { return p; }
int main () {
  A(); // calls `A::A(int=0)`
  A(i);  // calls `A::A(int=0)`
  A(*p); // <--- (1) same as local `A *p;`
  {
    A((*p));   // <--- (2) same as local `A *p;`
  }
  A (*foo());  // <--- (3) ??
  {
    A ((*foo()));  // <--- (4) ??
  }
}

预期至少A((*p))会调用A::A(int=0)。即使在*p周围放置多个大括号,也会将该语句视为A *p;
foo相关的语句也是如此,其中不调用构造函数A::A(int=0)。这是一个演示。

问题

  1. 为什么(2)和(4)都被视为声明
  2. 陈述(3)和(4)中对foo的描述是什么

在解析一个可能是声明或表达式的构造时,称为Most Vexing Parse模糊性,标准规定"解决方案是将任何可能是声明的构造视为声明"。

(2)和(4)都是有效的声明,因此必须将它们解析为声明。(3)和(4)都声明了A*()类型的函数foo,也就是"不带参数的函数返回指向A的指针"

6.8 Ambiguity resolution [stmt.ambig]

在涉及表达式语句和声明的语法中存在歧义:以函数式显式类型转换(5.2.3)作为其最左边子表达式的表达式语句可能与第一个声明符以(.开头的声明无法区分。在这些情况下,该语句是一个声明。[注意:为了消除歧义,可能必须检查整个语句,以确定它是表达式语句还是声明。这消除了许多示例的歧义。[示例:假设T是一个简单的类型说明符(7.1.5),

T(a)->m = 7; // expression-statement
T(a)++; //expression-statement
T(a,5)<<c; //expression-statement
T(*d)(int); //declaration
T(e)[5]; //declaration
T(f) = { 1, 2 }; // declaration
T(*g)(double(3)); // declaration

在上面的最后一个例子中,g是指向T的指针,它被初始化为double(3)。这当然是由于语义的原因造成的,但这并不影响句法分析--结束示例]

8.2 Ambiguity resolution [dcl.ambig.res]

6.8中提到的函数样式转换和声明之间的相似性所引起的歧义也可能发生在声明的上下文中。在这种情况下,可以在参数名周围有一组冗余圆括号的函数声明和将函数样式转换为初始值设定项的对象声明之间进行选择。正如6.8中提到的歧义一样,决议是将任何可能是声明的构造视为声明。[注意:声明可以通过非函数样式的强制转换、表示初始化的=或删除参数名称周围多余的括号来显式消除歧义。][示例:

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 z = int(a); // object declaration
}

--结束示例]

为什么(2)和(4)都被视为声明?

声明中的括号可用于更改声明符中的关联顺序,并可能将构造的含义从声明更改为表达式。它们具有与[]相同的优先级,并且从左到右分组。

例如:

int*a[1];   // brackets have higher precedence - this is an array of pointers
int(*a)[1]; // pointer to an array

现在,如果考虑A*p;A(*p);,这里的括号是多余的,因为它们没有改变解析方式。添加更多它们并不会改变任何事情——它仍然是一个有效的声明。

语句(3)和(4)中对foo的描述是什么?

声明与A* foo(); 相同

最新更新