24

Somebody had asked the other day why something compiles with clang, but not with gcc. I intuitively understood what was happening and was able to help the person, but it got me wondering -- according to the standard, which compiler was correct? Here is a boiled down version of the code:

#include <iostream>
#include <string>

class foo
{
public:
    foo(const std::string& x):
        name(x)
    { }
    foo& operator()(const std::string& x)
    {
        std::cout << name << ": " << x << std::endl;
        return (*this);
    }
    std::string name;
};

int main()
{
    std::string x = "foo";
    foo(x)("bar")("baz");
    return 0;
}

This compiles fine with clang++, but g++ gives the following error:

runme.cpp: In function ‘int main()’:
runme.cpp:21:11: error: conflicting declaration ‘foo x’
    foo(x)("bar")("baz");
        ^
runme.cpp:20:17: error: ‘x’ has a previous declaration as ‘std::string x’
    std::string x = "foo";

If I add a pair of parentheses in line 21, g++ is happy:

(foo(x))("bar")("baz");

In other words, g++ interprets this line as:

foo x ("bar")("baz");

Methinks itsa bug in g++, but again, I wanted to ask the standard experts, which compiler got it wrong?

PS: gcc-4.8.3, clang-3.5.1

curiousguy
  • 8,038
  • 2
  • 40
  • 58

2 Answers2

17

As far as I can tell this is covered in the draft C++ standard section 6.8 Ambiguity resolution which says that there can be an ambiguity between expression statements and declarations and says:

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.6),

and gives the following examples:

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

and then says:

The remaining cases are declarations. [ Example:

class T {
    // ...
   public:
    T();
    T(int);
    T(int, int);
};
T(a); // declaration
T(*b)(); // declaration
T(c)=7; // declaration
T(d),e,f=3; // declaration
extern int h;
T(g)(h,2); // declaration

—end example ] —end note ]

It seems like this case falls into the declaration examples in particular the last example seems to make the case in the OP, so gcc would be correct then.

Relevant section mentioned above 5.2.3 Explicit type conversion (functional notation) says:

[...] If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value, the type shall be a class with a suitably declared constructor (8.5, 12.1), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented temporary variable t, with the result being the value of t as a prvalue.

and 8.3 Meaning of declarators which says:

In a declaration T D where D has the form

( D1 ) 

the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration

T D1

Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.

Update

I was originally using N337 but if we look at N4296 section 6.8 was updated an it now includes the following note:

If the statement cannot syntactically be a declaration, there is no ambiguity, so this rule does not apply.

which means that gcc is incorrect since:

foo x ("bar")("baz");

can not be a valid declaration, I originally interpreted paragraph 2 as saying if you case begins with any of the following then it is declaration, which is perhaps how the gcc implementor interpreted as well.

I should have been more suspicious of paragraph 2 since the only normative part of paragraph 2 really said nothing with respect to paragraph 1 and seems to place a requirement on an example which is not normative. We can see that that statement form paragraph 2 is now actually a note which makes much more sense.

As T.C. noted below, paragraph 2 was actually never normative, it just appeared that way and he linked to the change that fixed it.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Thanks Shafik. Would creating a temporary `foo(x)` fall under _function-style explicit type conversion_? I shouldn't think so... – Super-intelligent Shade Feb 02 '15 at 17:45
  • @InnocentBystander: No, I think it's a simple declaration. It's parsed as `foo x;` – Mooing Duck Feb 02 '15 at 17:51
  • @InnocentBystander added more details from `5.2.3` which covers that. – Shafik Yaghmour Feb 02 '15 at 18:30
  • Wouldn't the additional function call operation force the parse as a declaration to fail, and therefore choose the expression interpretation? – Ben Voigt Feb 02 '15 at 19:01
  • @BenVoigt I forgot to add the additional set of examples, I just added them and the last one seems identical to the OPs as far as I can tell. – Shafik Yaghmour Feb 02 '15 at 19:24
  • @Steve similar but the most vexing parse is covered by section `8.2` – Shafik Yaghmour Feb 02 '15 at 22:17
  • 2
    `T(g)(h,2);` is very different to `T(g)(h)(2);` – M.M Feb 02 '15 at 22:48
  • 1
    `foo(x)("bar")` is a valid declaration. `foo(x)("bar")("baz");` can't be. – T.C. Feb 03 '15 at 03:12
  • @MattMcNabb I agree with you I have not been on SO for a long while after I answered but I realized later on that paragraph `2` seemed wrong and when I looked at `N4296` there were changes that made me realize I misinterpreted. I upvoted your answer as well. I wish I had a chance to update my answer earlier. – Shafik Yaghmour Feb 03 '15 at 15:35
  • @T.C. agreed, I discovered my mistake when I looked at `N4296` I was not able to fix it until now. – Shafik Yaghmour Feb 03 '15 at 15:39
  • @MattMcNabb, good catch. I've upvoted you as well. Shafik, thanks for updating the answer – Super-intelligent Shade Feb 03 '15 at 16:03
  • 6.8p2 in N3337 isn't actually normative - though it looked that way. The paragraph started in the middle of a note. (The editorial change between N3337 and N4296/N4140 you cited was made [here](https://github.com/cplusplus/draft/commit/4a58c6824c12ee4461edd9f5c891d050feca9032).) – T.C. Feb 03 '15 at 16:05
  • @T.C. that makes more sense, thanks for finding that. BTW, how did you find that commit? – Shafik Yaghmour Feb 03 '15 at 18:21
  • @ShafikYaghmour Blame. – T.C. Feb 03 '15 at 18:22
5

If we remove the line

std::string x = "foo";

then g++ complains about:

foo(x)("bar")("baz");

with the syntax error:

foo.cc:20:18: error: expected ',' or ';' before '(' token
     foo(x)("bar")("baz");

I do not see how foo (x)("bar")("baz"); could be a valid declaration, and apparently g++ can't either. The line foo x("bar")("baz"); is rejected with the same error.

The "ambiguity resolution" mentioned in Shafik's post only kicks in when the expression-statement is syntactically indistinguishable from a declaration. However in this case it is not a valid declaration syntax so there is no ambiguity, it must be an expression-statement.

g++ fails to process the line as an expression-statement so it is a g++ bug.

This is eerily similar to this g++ bug recently discussed on SO; it seems that g++ is perhaps deciding too soon in processing that the line must be a declaration .

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365