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?
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-volatileconst 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). Astatic data
member of literal type can be declared in the class definition with theconstexpr
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. [..]