Search code examples
c++pointerslanguage-lawyerdereferencemost-vexing-parse

Most vexing parse and pointer indirection/dereferencing


Minimal code:

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) ??
  }
}

Was expecting at least A((*p)) would invoke A::A(int=0). Even putting multiple braces around *p, treats the statement as A *p;.
The same holds true for foo related statement, where the constructor A::A(int=0) is not called. Here is a demo.

Question:

  1. Why are even (2) and (4) being treated as declarations?
  2. What is the descriptions of foo in statements (3) and (4)?

Solution

  • When parsing a construct that could either be a declaration or an expression - known as the Most Vexing Parse ambiguity - the standard says "the resolution is to consider any construct that could possibly be a declaration a declaration".

    Both (2) and (4) are valid declarations, therefore they must be parsed as declarations. Both (3) and (4) declare a function foo of type A*() aka "function taking no parameter returning pointer to A"

    6.8 Ambiguity resolution [stmt.ambig]

    There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion (5.2.3) as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration. [Note: To disambiguate, the whole statement might have to be examined to determine if it is an expression-statement or a declaration. This disambiguates many examples. [Example: assuming T is a simple-type-specifier (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
    

    In the last example above, g, which is a pointer to T, is initialized to double(3). This is of course illformed for semantic reasons, but that does not affect the syntactic analysis. —end example]

    8.2 Ambiguity resolution [dcl.ambig.res]

    The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration. [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ] [Example:

    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
    }
    

    —end example]