Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined reference to static const int

Tags:

c++

gcc

I ran into an interesting issue today. Consider this simple example:

template <typename T> void foo(const T & a) { /* code */ }  // This would also fail // void foo(const int & a) { /* code */ }  class Bar { public:    static const int kConst = 1;    void func()    {       foo(kConst);           // This is the important line    } };  int main() {    Bar b;    b.func(); } 

When compiling I get an error:

Undefined reference to 'Bar::kConst' 

Now, I'm pretty sure that this is because the static const int is not defined anywhere, which is intentional because according to my understanding the compiler should be able to make the replacement at compile-time and not need a definition. However, since the function takes a const int & parameter, it seems to be not making the substitution, and instead preferring a reference. I can resolve this issue by making the following change:

foo(static_cast<int>(kConst)); 

I believe this is now forcing the compiler to make a temporary int, and then pass a reference to that, which it can successfully do at compile time.

I was wondering if this was intentional, or am I expecting too much from gcc to be able to handle this case? Or is this something I shouldn't be doing for some reason?

like image 568
JaredC Avatar asked Mar 22 '11 13:03

JaredC


People also ask

What is a static const int?

“static const” is basically a combination of static(a storage specifier) and const(a type qualifier). The static determines the lifetime and visibility/accessibility of the variable.

Which is better #define or const int?

In general, const is a better option if we have a choice and it can successfully apply to the code. There are situations when #define cannot be replaced by const. For example, #define can take parameters (See this for example). #define can also be used to replace some text in a program with another text.

How do you initialize static constant characteristics of a class?

Static data members of a class in namespace scope have external linkage. The initializer for a static data member is in the scope of the class declaring the member. A static data member can be of any type except for void or void qualified with const or volatile. You cannot declare a static data member as mutable.


2 Answers

It's intentional, 9.4.2/4 says:

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

When you pass the static data member by const reference, you "use" it, 3.2/2:

An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19), is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.

So in fact, you "use" it when you pass it by value too, or in a static_cast. It's just that GCC has let you off the hook in one case but not the other.

[Edit: gcc is applying the rules from C++0x drafts: "A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.". The static cast performs lvalue-rvalue conversion immediately, so in C++0x it's not "used".]

The practical problem with the const reference is that foo is within its rights to take the address of its argument, and compare it for example with the address of the argument from another call, stored in a global. Since a static data member is a unique object, this means if you call foo(kConst) from two different TUs, then the address of the object passed must be the same in each case. AFAIK GCC can't arrange that unless the object is defined in one (and only one) TU.

OK, so in this case foo is a template, hence the definition is visible in all TUs, so perhaps the compiler could in theory rule out the risk that it does anything with the address. But in general you certainly shouldn't be taking addresses of or references to non-existent objects ;-)

like image 72
Steve Jessop Avatar answered Sep 20 '22 11:09

Steve Jessop


If you're writing static const variable with initializer inside class declaration it's just like as if you've written

class Bar {       enum { kConst = 1 }; } 

and GCC will treat it the same way, meaning that it does not have an address.

The correct code should be

class Bar {       static const int kConst; } const int Bar::kConst = 1; 
like image 20
pelya Avatar answered Sep 21 '22 11:09

pelya