views:

565

answers:

6

Can anyone explain why the following code does not compile (on g++ (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-49))?

struct X {
public:
   enum State { A, B, C };

   X(State s) {}
};

int main()
{
   X(X::A);
}

The message I get is:

jjj.cpp: In function 'int main()':
jjj.cpp:10: 'X X::A' is not a static member of 'struct X'
jjj.cpp:10: no matching function for call to 'X::X()'
jjj.cpp:1: candidates are: X::X(const X&)
jjj.cpp:5: X::X(X::State)`

Is this bad code or a compiler bug?

Problem solved by Neil+Konrad. See the comments to Neil's answer below.

+10  A: 

You've forgot the variable name in your definition:

int main()
{
   X my_x(X::A);
}

Your code confuses the compiler because syntactically it can't distinguish this from a function declaration (returning X and passing X::A as an argument). When in doubt, the C++ compiler always disambiguates in favour of a declaration.

The solution is to introduce redundant parentheses around the X since the compiler forbids parentheses around types (as opposed to constructo calls etc.):

(X(X::A));
Konrad Rudolph
Ah, no, actually. I'm trying to create a temporary. If I add another constructor, say, X(int a) {}, I can do X(5); and it compiles fine. It's only the enum that causes trouble.
Ari
Then heed Neil's advice.
Konrad Rudolph
I've updated my answer, and given an alternative to Neil's solution.
Konrad Rudolph
Please see my comment to neil.
Ari
+8  A: 
X(X::A);

is being seen a s a function declaration. If you really want this code, use:

(X)(X::A);
anon
The parentheses certainly solve the problem, but I'm not sure how this can be seen as a function declaration, given that X::A is not a type. Syntactically, it could be a type, but then X x(X::A) would be just as ambiguous.
Ari
My reading of the error message is that the compiler thinks it's an anonymous object being copy-constructed. That's why it's talking about "X X::A" and not plain "X::A". But I can't understand why it thinks that.
Ari
Ari: I'm actually confused by the error message myself because it's the same when you omit all parentheses, and this is clearly (for the compiler) a variable declaration (A of type X, inside scope X). But it *should* be treated as a function declaration by the compiler, according to the standard.
Konrad Rudolph
Perhaps I should have said "trying to see it as a declaration". Obviously if it could, there would be no error - it just wouldn't do what you expected.
anon
Konrad, I think you nailed it. It's disregarding the parentheses and treating it as a var decl. It's the same issue that explains the need for `typename` in templates.
Ari
P.S. "Disregard" is not the right term here. Var decls are allowed to contain parens.
Ari
X(X::A) <- that isn't a function declaration tho. It can't, because there is no name. It's seen as the declaration of a static object of X (parens are redundant): X X::A; thus, it thinks A is an object of type X in class X, and is looking for a default constructor of class X.
Johannes Schaub - litb
(there are two ambiguities: an expression statement that also syntactically looks like a declaration ( { int a; int(a); } << bug, redefinition of a ) as is the case here. and secondly, a declaration that can declare both a function and an object). both issues are because redundant parens are allowed
Johannes Schaub - litb
A: 

You should declare an object as

X x(X::A);

Bug in your code.

Marcin Gil
A: 

Either of these two lines work for me:

X obj(X::A);
X obj2 = X(X::A);

As Neil Butterworth points out, X(X::A) is being treated as a function declaration. If you really want an anonymous object, (X)(X::A) will construct an X object and immediately delete it.

Chris AtLee
A: 

You could, of course, just do something like this:

int main()
{
    // code
    {
    X temp(X::A);
    }
    // more code
}

This would be more readable and basically have the same effect.

rlbond
+1  A: 

Just to make it crystal clear what happens. Look at this example

int main() {
    float a = 0;
    {
        int(a); // no-op?
        a = 1;
    }
    cout << a;
}

What will it output? Well, it will output 0. The int(a) of above can be parsed in two different ways:

  • Cast to int and discard the result
  • Declare a variable called a. But ignore the parentheses around the identifier.

The compiler, when such a situation appears where a function-style cast is used in a statement and it looks like a declaration too, will always take it as a declaration. When it can't syntactically be a declaration (the compiler will look at the whole line to determine that), it will be taken to be an expression. Thus we are assigning to the inner a above, leaving the outer a at zero.

Now, your case is exactly that. You are trying (accidentally) to declare an identifier called A within a class called X:

X (X::A); // parsed as X X::A;

The compiler then goes on to moan about a not declared default constructor, because the static, as it assumes it to be, is default constructed. But even if you had a default constructor for X, it of course is still wrong because neither A is a static member of X, nor a static of X can be defined/declared at block scope.

You can make it not look like a declaration by doing several things. First, you can paren the whole expression, which makes it not look like a declaration anymore. Or just paren the type that is cast to. Both of these disambiguations have been mentioned in other answers:

(X(X::A)); (X)(X::A)

There is a similar, but distinct ambiguity when you try to actually declare an object. Look at this example:

int main() {
    float a = 0;
    int b(int(a)); // object or function?
}

Because int(a) can be both the declaration of a parameter called a and the explicit conversion (cast) of the float-variable to an int, the compiler decides again that that is a declaration. Thus, we happen to declare a function called b, which takes an integer argument and returns an integer. There are several possibilities how to disambiguate that, based on the disambiguation of above:

int b((int(a))); int b((int)a);
Johannes Schaub - litb