216

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;
}
0

9 Answers 9

206

You need to actually define 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.

7
  • 3
    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
    Commented Nov 7, 2008 at 18:08
  • 14
    This answer only addresses the first part of the question. The second part is much more interesting: Why does adding a NOP cast make it work without requiring the external declaration? Commented Feb 1, 2011 at 0:48
  • 36
    I just spent a good bit of time figuring out that if the class definition is in a header file, then the allocation of the static variable should be in the implementation file, not the header.
    – shanet
    Commented Jul 14, 2012 at 3:06
  • 1
    @shanet: Very good point--I should have mentioned that in my answer!
    – Drew Hall
    Commented Jul 14, 2012 at 3:10
  • But if I declare it as const, is it not possible for me to change the value of that variable?
    – Namratha
    Commented Nov 27, 2012 at 4:57
78

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.

4
  • 2
    thanks, that helped... that could qualify for stackoverflow.com/questions/1995113/strangest-language-feature if it isn't there already... Commented Dec 3, 2010 at 13:45
  • 1
    Also worth noting that MSVC accepts the non-cast version without complaints.
    – porges
    Commented Jun 26, 2012 at 23:49
  • 4
    -1: This is simply not true. You're still supposed to define static members initialised inline, when they are odr-used somewhere. That compiler optimisations may get rid of your linker error doesn't change that. In this case your lvalue-to-rvalue conversion (thanks to the (int) cast) occurs in the translation unit with perfect visibility of the constant, and the Foo::MEMBER is no longer odr-used. This is in contrast with the first function call, where a reference is passed around and evaluated elsewhere. Commented Oct 6, 2014 at 22:12
  • What about void push_back( const T& value );? const&'s can bind with rvalues.
    – Kostas
    Commented Feb 24, 2020 at 21:38
63

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 );
5
  • 5
    +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... :) Commented May 29, 2009 at 10:38
  • 4
    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? Commented May 29, 2009 at 10:51
  • 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. Commented May 29, 2009 at 12:47
  • @RichardCorden : how does the unary + resolved that ? Commented Jun 28, 2018 at 15:10
  • 1
    @Blood-HaZaRd: Before rvalue references the only overload of push_back was a const &. Using the member directly resulted in the member being bound to the reference, which required it had an address. However, adding the + creates a temporary with the value of the member. The reference then binds to that temporary rather than requiring the member have an address. Commented Jul 13, 2018 at 10:48
10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;
0
9

In C++17, there is an easier solution using inline variables:

struct Foo{
    inline static int member;
};

This is a definition of member, not just its declaration. Similar to inline functions, multiple identical definitions in different translation units do not violate ODR. There is no longer any need to pick a favourite .cpp file for the definition.

0
3

Just some additional info:

C++ allows to "define" const static types of integral and enumeration types as class members. But this is actually not a definition, just an "initializiation-marker"

You should still write a definition of your member outside of the class.

If a non-volatile non-inline const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. The member shall still be defined in a namespace scope if it is odr-used in the program and the namespace scope definition shall not contain an initializer.

- [class.static.data] p4

2

With C++11, the above would be possible for basic types as

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

The constexpr part creates a static expression as opposed to a static variable - and that behaves just like an extremely simple inline method definition. The approach proved a bit wobbly with C-string constexprs inside template classes, though.

2
  • This turned out to be essential for me, because the "static const int MEMBER = 1;" is needed to use MEMBER in switches, whilst the external declaration is needed to use it in vectors, and you cannot have both at the same time. But the expression you give here does work for both, at least with my compiler.
    – Ben Farmer
    Commented Sep 16, 2019 at 15:33
  • @BenFarmer: This approach stops needing an out-of-class definition in C++17 (because it’s implicitly inline, as variables can be in that version). Commented Nov 21, 2020 at 3:06
0

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.

1
  • I think you're answering your own question: The cast works because it creates (a temporary) reference. Commented Nov 30, 2011 at 15:43
-1

Regarding the second question: push_ref takes reference as a parameter, and you cannot have a reference to static const memeber of a class/struct. Once you call static_cast, a temporary variable is created. And a reference to this object can be passed, everything works just fine.

Or at least my colleague who resolved this said so.

Not the answer you're looking for? Browse other questions tagged or ask your own question.