Weird Undefined Symbols of Static Constants Inside a Struct/Class

Weird undefined symbols of static constants inside a struct/class

Definition needed

The code you have provided is non-standard. While you can provide initializers for const static int members directly in the class, you still need to provide separate definitions. It is weird, a kind of unexpected, but you are expected to write it like this:

#include <algorithm>

struct Foo
{
static const int A = 1;
static const int B = 2;
};

const int Foo::A;
const int Foo::B;

int main()
{
return std::min(Foo::A, Foo::B);
}

Quote from standard can be found in a similar question at const and static specifiers in c++

Why sometimes the code "works" without a definition?

As for why you can often get around even without providing the definition: if you are using those members only in constant expressions, compiler will always resolve them directly and there will be no access left for linker resolution. It is only when you use it in some way which cannot be handled by compiler directly, and only in such case the linker will detect the symbol is undefined. I guess this is probably a bug in the Visual Studio compiler, but given the nature of the bug I doubt it will be ever fixed.

Why your source falls into the "linker" category is something I do not see, one would need to dissect the std::min to understand that. Note: When I have tried it online with GCC, it worked, the error was not detected.

Alternative: use enum

Another alternative is to use enum. This version can also come handy when you hit an old compiler which does not support static const int "inline" initializers (such as was Visual Studio 6). Note however that with std::min you are hitting other problems with enums and you need to use an explicit instantiation or casting, or have both A and B in one named enum as in the answer by Nawaz:

struct Foo
{
enum {A = 1};
enum {B = 2};
};

int main()
{
return std::min<int>(Foo::A, Foo::B);
}

Standards

Note: even Stroustrup C++ FAQ gets this wrong and does not require the definition as strictly as the standard does:

You can take the address of a static member if (and only if) it has an out-of-class definition

The definition is required by a standard in 9.4.2:

C++03 wording:

The member shall still be defined in a name-space scope if it is used in the program and the namespace scope definition shall not contain an initializer

C++11 wording of 9.4.2 is a bit different:

3 The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program

3.2 says following about odr-use:

3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless x is an object that satisfies the requirements for appearing in a constant expression (5.19) and ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

4 Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

I have to admit I am not sure what the exact implications of C++11 wording are, as I fail to understand the odr-use rules.

Undefined reference to static class member

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.

static const int causes linking error (undefined-reference)

If you want to initialize it inside the struct, you can do it too:

struct Elem {
static const int value = 0;
};

const int Elem::value;

Undefined reference to static const int

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

static const in c++ class: undefined reference

std::min<int>'s arguments are both const int&(not just int), i.e. references to int. And you can't pass a reference to A::MY_CONST because it is not defined (only declared).

Provide a definition in the .cpp file, outside the class:

class A {
public:
static const int MY_CONST = 5; // declaration
};

const int A::MY_CONST; // definition (no value needed)

const static member initialization - inside vs outside class definition

What are the differences between the two initializations above? Why does one cause the error with stl containers?

The argument type of std::vector<int>::push_back() is int const&. Whenever a variable is used by reference or pointer, it must be defined.

A simple change to Class::f implementation will obviate the need to define Class::var.

void Class::f() {
std::vector<int> vec;
int v = var;
vec.push_back(v);
}

Here, var is not used by reference. Hence, there is no need to define Class::var.

Linker gives error undefined symbol for integral static const members used in certain contexts

If the compiler feels it needs the address of the static member variable, e.g., when binding the variable to a reference at some point, it will create a corresponding undefined symbol and you will have to define the member:

int const foo::TEST;

(in one translation unit). If the compiler only ever accesses a the value you can get away with not defining the object. Unless you need the type to be an int, you can use an enum instead and avoid the need for defining the member:

enum { TEST = 33 };

The term in the standard to look for is odr-used if I recall correctly.

Undefined reference to static const integral type

As a rule, all static const names should be defined in .cpp file if they are ODR used. Taking a reference to the argument is ODR using them. However, having violated this rule is undefined behavior, and MSVC not reporting an error is just one of the ways undefined behavior can be manifested.

As a practical consideration, you are likely to have the error when function is not inlined, and probably won't see it for the inlined functions. My guess is that inlining works differently with the level of optimization you are using for those compilers.



Related Topics



Leave a reply



Submit