123

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.

b4hand
  • 9,550
  • 4
  • 44
  • 49
HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • 4
    Works fine on Visual Studio 2010. – Puppy Jun 11 '10 at 20:33
  • 4
    This exact error is explained at https://gcc.gnu.org/wiki/VerboseDiagnostics#missing_static_const_definition – Jonathan Wakely Mar 25 '15 at 15:09
  • 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 Jul 14 '15 at 13:14
  • 1
    This question is a manifest of how poor the C++ answer to "do not use #defines for constants" still is. – Johannes Overmann Jul 19 '19 at 09:22
  • 1
    @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 Aug 04 '19 at 18:52
  • 1
    How do I use a static const int variable in another class? – Abdel Aleem Dec 17 '19 at 13:42
  • It looks like a dummy GCC linker rule to me... no need to overcomplicate something that is parsed correctly just for the GCC linker honestly. – Raffaello Dec 06 '21 at 14:55

7 Answers7

78

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.

glibg10b
  • 338
  • 1
  • 11
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • I see, that's interesting. In that case, what is the difference between providing the value at the point of declaration versus providing the value at the point of definition? Which one is recommended? – HighCommander4 Jun 11 '10 at 20:41
  • Well, I believe that you can get away without a definition so long as you never actually "use" the variable. If you only use it as a part of a constant expression then the variable is never used. Otherwise there doesn't seem to be a huge difference besides being able to see the value in the header - which may or may not be what you want. – Edward Strange Jun 11 '10 at 20:46
  • 2
    The terse answer is static const x=1; is an rvalue but not an lvalue. The value is available as a constant at compile time (you can dimension an array with it) static const y; [no initializer] must be defined in a cpp file and may be used either as an rvalue or an lvalue. – Dale Wilson Aug 17 '12 at 16:54
  • 2
    It would be nice if they could extend/improve this. initialized-but-not-defined objects should, in my opinion, be treated the same as literals. For example, we are allowed to bind a literal `5` to a `const int&`. So why not treat the OP's `test::N` as the corresponding literal? – Aaron McDaid Jul 14 '15 at 11:38
  • Interesting explanation, thanks! This means that in C++ static const int is still no replacement for integer #defines. enum is always only signed int, so one has to use enum classes for individual constants. It would be quite obvious to me to degenerate a constant declaration with constant and know values to a literal constant in which way this would compile without problems. C++ has a long way to go ... – Johannes Overmann Jul 19 '19 at 09:27
  • Indeed, the problem with references is that we demand a storage for the object. Fun facts: 1) with optimizations turned on the call to `std::min(9, test::N)` gracefully compiles; then I thought putting parenthesis around `test::N` would make the expression rvalue but -- not that simple -- 2) the code `std::min(9, (test::N))` doesn't compile; then rvalue reference is still a _reference_, so 4) `std::min(9, reinterpret_cast(test::N))` or `std::min(9, std::move(test::N))` are no-go too; and finally 5) `std::min(9, test::N+0)` does the dirty trick. [Check out](http://cpp.sh/6zjcr) – Wormer Aug 04 '19 at 18:40
58

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.

richardthe3rd
  • 353
  • 2
  • 7
  • 2
    `std::min` takes its parameters by reference, which is why a definition is required. – Rakete1111 Sep 18 '17 at 19:22
  • How would I write the definition if AE is a template class AE and c7 is not an int but T::size_type? I have the value initialized to "-1" in the header but clang says undefined value and I don't know how to write the definition. – Fabian Aug 15 '19 at 18:04
  • @Fabian I am traveling and on a phone and a bit busy...but I would think that your comment sounds like it would be best written as a new question. Write up a [MCVE](https://stackoverflow.com/help/minimal-reproducible-example) including the error you get, also maybe throw in what gcc says. I bet people would tell you quickly what's what. – HostileFork says dont trust SE Aug 15 '19 at 23:09
  • @HostileFork: When writing a MCVE, you sometimes figure the solution out yourself. For my case the answer is `template const typename AE::KeyContainer::size_type AE::c7;` where KeyContainer is a typedef of std::vector. One must list all template parameters and write typename because it's a dependent type. Maybe someone will find this comment useful. However, now I wonder how to export this in a DLL because the template class is of course in a header. Do I need to export c7??? – Fabian Aug 16 '19 at 14:54
26

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

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
  • 12,724
  • 1
  • 35
  • 46
  • 2
    And this would probably solve the problem. When N is used as a parameter for min() it will cause a temporary to be created rather than try to refer to a supposedly existing variable. – Edward Strange Jun 11 '10 at 20:49
  • This had the advantage that it can be made private. – Agostino Apr 10 '18 at 09:06
13

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.

Amardeep AC9MF
  • 18,464
  • 5
  • 40
  • 50
  • 2
    I am aware that you can *declare* a variable of any type inside the class declaration. I said that I thought static integer constants could also be *defined* inside the class declaration. Is this not the case? If not, why is it that the compiler does not give an error at the line where I try to define it inside the class? Moreover, why does the std::cout line not cause a linker error, but the std::min line does? – HighCommander4 Jun 11 '10 at 20:32
  • No, can't define static members in the class declaration because the initialization emits code. Unlike an inline function which also emits code, a static definition is globally unique. – Amardeep AC9MF Jun 11 '10 at 20:35
  • @HighCommander4: You can supply an initializer for the `static const` integral member in the class definition. But that still *does not define* that member. See Noah Roberts answer for details. – AnT stands with Russia Jun 11 '10 at 20:39
9

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

karadoc
  • 2,641
  • 22
  • 21
  • 5
    or even `std::min(9, +test::N);` – Tomilov Anatoliy Apr 19 '13 at 05:31
  • Here's the big question though: is all this optimal? I don't know about you guys, but my big attraction to skipping the definition is that it should take up no memory and no overhead in using the const static. – Opux Apr 26 '17 at 21:53
8

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 ;).

Carlo Wood
  • 5,648
  • 2
  • 35
  • 47
  • And what if you need to pass the value as an argument of type 'const int&' like in the example? :-) – Wormer Aug 12 '19 at 15:30
  • That works fine. You're not *instantiating* N that way, merely passing a const reference to a temporary. https://wandbox.org/permlink/JWeyXwrVRvsn9cBj – Carlo Wood Aug 12 '19 at 18:54
  • C++17 maybe, not C++14, and even not C++17 in earlier versions of gcc 6.3.0 and lower, it's not a standard thing. But thanks for mentioning this. – Wormer Aug 13 '19 at 15:01
  • Ah yes, you are right. I didn't try c++14 on wandbox. Oh well, that is the part where I said "This theoretically still requires you to define the constant". So, you are right that it is not 'standard'. – Carlo Wood Aug 20 '19 at 18:16
3

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

fredoverflow
  • 256,549
  • 94
  • 388
  • 662