Reputation: 911
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?
Upvotes: 3
Views: 2146
Reputation: 126432
That is because std::map::operator[]
takes its argument by reference, which makes your variable odr-used (see paragraph 3.2/3 of the C++11 Standard).
In short, the whole thing boils down to the fact that the compiler needs to know the address of an object when it needs to bind a reference to it, and that makes it impossible for it to treat that object just like a pure value and perform inlining.
In that case, you need to provide a definition of your static data member at global namespace scope, so that the compiler knows what region of storage that object occupies (i.e. what its address is):
template<int T>
const int A::Type;
Per paragraph 9.4.2/3 of the C++11 Standard:
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). [ ... ] 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.
In the first version of your program, on the other hand, you were only using the value of your static data member, which means Type
was not odr-used, and a definition at namespace scope was not needed.
Upvotes: 3
Reputation: 385134
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. [..]
Upvotes: 7