110
votes

My understanding is that C++ allows static const members to be defined inside a class so long as it's an integer type.

Why, then, does the following code give me a linker error?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

The error I get is:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Interestingly, if I comment out the call to std::min, the code compiles and links just fine (even though test::N is also referenced on the previous line).

Any idea as to what's going on?

My compiler is gcc 4.4 on Linux.

7
Works fine on Visual Studio 2010.Puppy
This exact error is explained at gcc.gnu.org/wiki/…Jonathan Wakely
In the particular case of char, you can define it instead as constexpr static const char &N = "n"[0];. Note the &. I guess this works because literal strings are defined automatically. I'm kinda worried about this though - it might behave strangely in a header file among different translation units, as the string will probably be at multiple different addresses.Aaron McDaid
This question is a manifest of how poor the C++ answer to "do not use #defines for constants" still is.Johannes Overmann
@JohannesOvermann In this regard, I want to mention the use of inline for global variables since C++17 inline const int N = 10, which to my knowledge still has a storage somewhere defined by linker. Keyword inline could also be used in this case to provide static variable definition inside the class definition test.Wormer

7 Answers

74
votes

My understanding is that C++ allows static const members to be defined inside a class so long as it's an integer type.

You are sort of correct. You are allowed to initialize static const integrals in the class declaration but that is not a definition.

Interestingly, if I comment out the call to std::min, the code compiles and links just fine (even though test::N is also referenced on the previous line).

Any idea as to what's going on?

std::min takes its parameters by const reference. If it took them by value you'd not have this problem but since you need a reference you also need a definition.

Here's chapter/verse:

9.4.2/4 - If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

See Chu's answer for a possible workaround.

51
votes

Bjarne Stroustrup's example in his C++ FAQ suggests you are correct, and only need a definition if you take the address.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

He says "You can take the address of a static member if (and only if) it has an out-of-class definition". Which suggests it would work otherwise. Maybe your min function invokes addresses somehow behind the scenes.

24
votes

Another way to do this, for integer types anyway, is to define constants as enums in the class:

class test
{
public:
    enum { N = 10 };
};
11
votes

Not just int's. But you can't define the value in the class declaration. If you have:

class classname
{
    public:
       static int const N;
}

in the .h file then you must have:

int const classname::N = 10;

in the .cpp file.

9
votes

Here's another way to work around the problem:

std::min(9, int(test::N));

(I think Crazy Eddie's answer correctly describes why the problem exists.)

6
votes

As of C++11 you can use:

static constexpr int N = 10;

This theoretically still requires you to define the constant in a .cpp file, but as long as you don't take the address of N it is very unlikely that any compiler implementation will produce an error ;).

3
votes

C++ allows static const members to be defined inside a class

Nope, 3.1 §2 says:

A declaration is a definition unless it declares a function without specifying the function's body (8.4), it contains the extern specifier (7.1.1) or a linkage-specification (7.5) and neither an initializer nor a functionbody, it declares a static data member in a class definition (9.4), it is a class name declaration (9.1), it is an opaque-enum-declaration (7.2), or it is a typedef declaration (7.1.3), a using-declaration (7.3.3), or a using-directive (7.3.4).