Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined reference to static constexpr string (except if it's a pointer)

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.

like image 544
Peregring-lk Avatar asked Dec 12 '22 00:12

Peregring-lk


2 Answers

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.

like image 177
Brian Bi Avatar answered May 19 '23 05:05

Brian Bi


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.

like image 40
chris Avatar answered May 19 '23 06:05

chris