This work:
template<typename T> struct Something
{ static constexpr const char* str = "int"; };
int main()
{ std::cout << Something<int>::str << std::endl; }
But it doesn't:
template<typename T> struct Something
{ static constexpr const char str[] = "int"; };
int main()
{ std::cout << Something<int>::str << std::endl; }
gcc-4.8
says: "undefined reference to Something<int>::str
".
This error can be solved defining the static member outside of the class:
template<typename T>
constexpr const char Something<T>::name[];
Why isn't it neccesary with pointers but with arrays? Both are static constexpr
members after all.
An object or function must be defined if it is odr-used. In some cases objects and functions are not odr-used and in those cases you don't have to define them. Whether or not the declaration of a static class member has an initializer, it is still not a definition. In all cases the rule is that an out-of-class definition at an enclosing namespace scope is required if the static member is odr-used.
The intuition is that "odr-used" means "the linker needs its address". If a constexpr
variable is only used in ways that require its value---and never its address---then it may avoid being odr-used. In such cases the compiler simply inlines its value, and it does not need to be defined, because the linker doesn't need its address. That's the case with const char* Something<int>::str
, but not const char Something<int>::str[]
.
"But they're the same!", you shout. Not so. For when str
is a const char*
, its value is the address of the string literal "int"
. The address of the string literal is needed, but not the address of str
itself. The former is the value of str
and it satisfies the requirements for not being odr-used; the compiler can just inline it. But when str
is a const char[]
, its value is the string literal "int"
itself. When you try to output it using istream::operator<<
, it is implicitly converted into a const char*
. But the const char*
's value is the address of the string literal, that is, the address of Something<int>::str
. Therefore in this case Something<int>::str
is odr-used; its address is needed.
There is logic in the standard that can be used to determine precisely when a variable is odr-used ([basic.def.odr]). But I'm not going to quote it because it's the most confusing section of the entire standard. I will say that in the case where you have const char* Something<int>::str
, the lvalue-to-rvalue conversion is immediately applied, which is one of the conditions for it to not be odr-used; and in the case where you have const char Something<int>::str[]
, the array-to-pointer conversion is immediately applied, and that doesn't satisfy the condition.
N3485, §3.2 [basic.def.odr]/3 says:
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).
In the case of the array, it must undergo an array-to-pointer conversion in order to fit the overload set of operator<<
. This is not listed in the above text, and so the array str
is odr-used.
§9.4.2 [class.static.data]/3 says (emphasis mine):
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.
Since the array str
is odr-used, it must be defined outside of the class. The pointer str
is not odr-used, and thus does not have to be.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With