tags:

views:

10355

answers:

7

Hey,
Can anyone explain why following code won't compile? At least on g++ 4.2.4.
And more interesting, why it will compile when I cast MEMBER to int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}
A: 

Perhaps because you didn't specify a type for your vector?

JaredPar
Nah, I just tested it, it isn't a template issue, even when it is written std::vector<int>, it still happens...very odd
Evan Teran
A: 

@JaredPar I did. I copypasted the code but anything in angle brackets just disappears. Don't know why.

Pawel Piatkowski
I ran into that problem a lot when i first started posting here. I find the best way is to paste the code, select it all and hit CTRL+K. That will take care of proper escaping and such.
JaredPar
thanks :)10 characters min rule is annoying ;)
Pawel Piatkowski
+18  A: 

You need to actually allocate the static member somewhere (after the class definition). Try this:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

That should get rid of the undefined reference.

Drew Hall
Good point, inline static const integer initialization creates a scoped integer constant which you can't take the address of, and vector takes a reference param.
Evan Teran
+10  A: 

The problem comes because of an interesting clash of new C++ features and what you're trying to do. First, let's take a look at the push_back signature:

void push_back(const T&)

It's expecting a reference to an object of type T. Under the old system of initialization, such a member exists. For example, the following code compiles just fine:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

This is because there is an actual object somewhere that has that value stored in it. If, however, you switch to the new method of specifying static const members, like you have above, Foo::MEMBER is no longer an object. It is a constant, somewhat akin to:

#define MEMBER 1

But without the headaches of a preprocessor macro (and with type safety). That means that the vector, which is expecting a reference, can't get one.

Douglas Mayle
This makes complete sense
Evan Teran
A: 

No idea why the cast works, but Foo::MEMBER isn't allocated until the first time Foo is loaded, and since you're never loading it, it's never allocated. If you had a reference to a Foo somewhere, it would probably work.

Paul Tomblin
+5  A: 

The C++ standard requires a definition for your static const member if the definition is somehow needed.

The definition is required, for example if it's address is used. push_back takes its parameter by const reference, and so strictly the compiler needs the address of your member and you need to define it in the namespace.

When you explicitly cast the constant, you're creating a temporary and it's this temporary which is bound to the reference (under special rules in the standard).

This is a really interesting case, and I actually think it's worth raising an issue so that the std be changed to have the same behaviour for your constant member!

Although, in a weird kind of way this could be seen as a legitimate use of the unary '+' operator. Basically the result of the unary + is an rvalue and so the rules for binding of rvalues to const references apply and we don't use the address of our static const member:

v.push_back( +Foo::MEMBER );
Richard Corden
+1. Yes it's certainly weird that for an object x of type T, the expression "(T) x" can be used to bind a const ref while plain "x" can't. I love your observation about "unary +"! Who would have thought that poor little "unary +" actually had a use... :)
j_random_hacker
Thinking about the general case... Is there any other type of object in C++ that has the property that it (1) can be used as an lvalue only if it has been defined but (2) can be converted to an rvalue without being defined?
j_random_hacker
Good question, and at least at the moment I cannot think of any other examples. This is probably only here because the committee were mostly just reusing existing syntax.
Richard Corden
A: 

I don't follow Douglas' answer. If it is true, shouldn't the following code fail?

#include <vector>

#define FOO_MEMBER 1

int main() {
    std::vector<int v;
    v.push_back(1);
    v.push_back(FOO_MEMBER);
    return 0;
}
Matt McClellan
No. After preprocessing, the first push_back call is exactly the same as the second one.
leod
Right. If Douglas' answer is correct, then I shouldn't be able to use a literal. Drew's answer is correct.
Matt McClellan