33

Either I'm very tired or something weird is happening that I'm not aware of, because the code below is resulting in undefined symbols for Foo::A and Foo::B when linking. This is minimized as much as I could from a larger project, but shows the essence of what I'm looking at.

#include <algorithm>

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

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

Without the std::min function template it works fine, i.e. just return Foo::A. Also fine is when defining the static ints outside a class/struct (global in this simple case). However, as soon as they're inside like this, the linker cannot find them.

Can someone explain what's happening?

Suma
  • 33,181
  • 16
  • 123
  • 191
murrekatt
  • 5,961
  • 5
  • 39
  • 63
  • 1
    possible duplicate of [C++ - defining static const integer members in class definition](http://stackoverflow.com/questions/3025997/c-defining-static-const-integer-members-in-class-definition) – ks1322 Mar 11 '14 at 09:03

6 Answers6

44

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.

Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191
  • 2
    I wasn't aware that integral constants needed this. I see this kind of code a lot, not necessarily written by me ;) Does this mean that one should never do what I showed and always init static constants in cpp files? – murrekatt Feb 03 '11 at 20:10
  • See here for more info: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.13 – fbrereto Feb 03 '11 at 20:13
  • You may init them in the .hpp (in the class) but you still should provide the separate definition. See the added link. – Suma Feb 03 '11 at 20:14
  • Thanks Suma for the added explanation. BTW, I'm on gcc on MacOSX. Will try at work tomorrow on some other compilers. – murrekatt Feb 03 '11 at 20:22
  • @Suma: Your enums are unnamed. That should not work. See here yourself : http://www.ideone.com/n1hel – Nawaz Feb 03 '11 at 20:32
  • Strange. Are you 100 % sure? Another non-standard extension I am not even aware of being non-standard? :( But is it really wrong? See http://www2.research.att.com/~bs/bs_faq2.html#in-class - the example there uses unnamed enum as well. – Suma Feb 03 '11 at 20:53
  • I think the problem is different. The std::min is not flexible enough to accept enum values like this. See the compilation error. Naming the enums does not help. See http://www.ideone.com/rY8y5 – Suma Feb 03 '11 at 20:55
  • 2
    The linker error probably occur because the `std::min` function takes it parameter by reference. The compiler is thus obliged to record that the address of the constant was used (maybe even if the function is inlined) so that every translation unit that need the address of the constant get the same value (a single object can't have multiple address in C++). – Sylvain Defresne Feb 03 '11 at 20:58
  • 1
    @Suma: the problem is that the compiler needs to know the "named" type when it instantiates `std::min` function template. I **didn't** say that you cannot define unnamed enums in general! – Nawaz Feb 04 '11 at 09:10
3

If you just want integral values,then you can define enum as well:

#include <algorithm>

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

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

This is more than enough. No declaration needed outside the class!

Online Demo : http://www.ideone.com/oE9b5

Nawaz
  • 353,942
  • 115
  • 666
  • 851
2

There are good answers here, but one additional thing to note is that the parameters of std::min() are references, which is what requires the addresses of the passed in variables, and since those variables don't make it to the object file for the compilation unit, the linker cannot resolve their addresses.

You are probably getting this in an non-optimized build, correct?

I bet that you won't get this with gcc if you enable optimizations. The call to std::min() will get inlined and the references will go away.

Also, if you were to assign Foo::A and Foo::B to two local variables right before the call to std::min(), this issue would also go away.

This isn't ideal, but if you don't own the code that defines the variables that are causing you this issue, then this is something you can consider.

Littm
  • 4,923
  • 4
  • 30
  • 38
stingoops
  • 51
  • 5
2

You must define the static constants outside the class definition.

struct Foo {
    static const int A;
    static const int B;
};

const int Foo::A = 1;
const int Foo::B = 2;
wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
1

Seeing as you're basically using struct as a namespace, why not just use namespace:

#include <algorithm>

namespace Foo
{
    const int A = 1;
    const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}
John Ripley
  • 4,434
  • 1
  • 21
  • 17
  • 1
    Thanks, but in the example I showed, I've minimized things from something larger where the constants were inside a class. Sure I can do something different, but I wanted to know THAT specific issue. – murrekatt Feb 04 '11 at 07:04
  • The point is, if you want to avoid placing a definition somewhere, then you can achieve that with a real namespace. – John Ripley Feb 04 '11 at 09:00
0

Another solution would be to inline your static variables, like this it will be available in the final translation unit which does away with the Undefined Symbols error.

Note this will only work in post C++11 AFAIK.

Moshe Rabaev
  • 1,892
  • 16
  • 31