Search code examples
c++templatesc++11g++undefined-reference

C++ template class static const variable member as map key gives undefined reference


I have a bunch of classes that have a static member that is an enum value. And I have a map somewhere else with this enum as key. Now if I use a template parameter in a function to access the map, I get an undefined reference.

To make it clear, here is a simplified non-working example :

template<int T>
struct A
  {
    static const int Type = T;
  }

template<class T>
void fun()
  {
    cout << map_[T::Type] << endl;
  }

map<int, string> map_{{1337, "1337"}};

main :

fun<A<1337>();

gives me (g++ 4.7) :

undefined reference to `(anonymous namespace)::A<1337>::Type'

However this :

template<class T>
void fun()
  {
    auto key = T::Type;
    cout << map_[key] << endl;
  }

Compile and prints 1337

Can someone explain me this behavior?


Solution

  • When you use T::Type, you must define it:

    template<int T>
    struct A
    {
       static const int Type = T;
    }
    
    template <int T>
    const int A<T>::Type;
    

    Yes, even though you provided its initialiser inline within A<T>!

    The reason that you may not have been aware of this, is the same reason that you don't get the same problem in your second case — due to the immediate lvalue-to-rvalue conversion, the standard allows the compiler to optimize the requirement to refer to Type at runtime, able instead to pick out the value at compile-time. The linker then has no need to search for the definition, and you get no error.


    [C++11: 9.4.2/2]: The declaration of a static data member in its class definition is not a definition and may be of an incomplete type other than cv-qualified void. The definition for a static data member shall appear in a namespace scope enclosing the member’s class definition. In the definition at namespace scope, the name of the static data member shall be qualified by its class name using the :: operator. The initializer expression in the definition of a static data member is in the scope of its class (3.3.7). [..]

    [C++11: 9.4.2/3]: If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.19). A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

    [C++11: 3.2/2]: [..] A variable 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. [..]