4

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)?
iammilind
  • 68,093
  • 33
  • 169
  • 336

2 Answers2

1

Why are even (2) and (4) being treated as declarations?

Parentheses in declarations can be used to change the association order in declarators and possibly change the meaning of the construct from declaration to expression. They have the same precedence as [] and group left to right.

For example:

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

Now if you consider A*p; and A(*p); the parentheses here are redundant because they didn't change the way this is parsed. Adding more of them doesn't change a thing - it is still a valid declaration.

What is the descriptions of foo in statements (3) and (4)?

The declarations are the same as A* foo();

jrok
  • 54,456
  • 9
  • 109
  • 141
  • 1
    This is not answering the question. Parentheses does matter in some of the cases (most vexing parse). e.g. Assume that there is a `class X` which has a constructor as `X::X(A);`. Now, `X x(A())` is different than `X x((A()))`. Former is declaring `x` as function, the later is declaring `x` as an object which takes `A` as an argument. I was expecting the same phenomena here, where I want to dereference `p` and call the constructor `A::A(int)`. – iammilind Aug 01 '13 at 13:25
  • You're right, I worded this poorly. What I mean is that parentheses in this particular construct (`A(*p);`) are redundant in the first place (and adding more of them doesn't change a thing). They only matter where they make a construct invalid as a declaration, as in your example. – jrok Aug 01 '13 at 13:30
  • @iammilind I changed the wording, better now? – jrok Aug 01 '13 at 14:19
  • Actually your answer is going in different direction. You can probably refer the thread about [most vexing parse](http://stackoverflow.com/questions/1424510/most-vexing-parse-why-doesnt-a-a-work) and [and other one](http://stackoverflow.com/questions/5926103/most-vexing-parsec), which shows that how the braces are important for compiler to know the nature of a construct. I was thinking that the same rules would apply to pointer dereferencing as well, which is not happening here. – iammilind Aug 01 '13 at 14:24
  • 1
    @iammilind I'm sure not I understand you. The point is that it is not a pointer dereference in the first place. Yes, it *can* be interpreted like one syntacticaly, but it is *also* a valid declaration. And when it could be both, it's always deemed a declaration. – jrok Aug 01 '13 at 14:32
  • Ok, here my question in other words: (Scenario-1) Consider `A` some valid class: Now in `X x(A())`, `x` is treated as function, while in `X x((A()));`, `x` is treated as an object due to extra braces. (Scenario-2) Consider `p` some valid pointer: Now in `X(*p)`, `p` is treated as pointer, while in `X((*p))` also it's treated as pointer only, even with extra braces. That's my question, why? – iammilind Aug 01 '13 at 14:37
  • I thought I already explained why. Because adding parentheses there (and I'm talking about transition between `X*p` -> `X(*p)`) doesn't change the way it is parsed (or "binding" of declarators or whatever you want to call it) and adding more pairs of parentheses doesn't change the situation. It *remains* a valid declaration of pointer to X. I can't explain better, sorry. – jrok Aug 01 '13 at 14:42
  • @iammilind: jrok already explained it. You have assumed that parentheses forced an expression in this case, but this is simply not true. – Lightness Races in Orbit Aug 01 '13 at 15:40
1

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]

willj
  • 2,991
  • 12
  • 24