对静态 constexpr 字符串的未定义引用(除非它是一个指针)

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

这项工作:

template<typename T> struct Something
{ static constexpr const char* str = "int"; };

int main()
{ std::cout << Something<int>::str << std::endl; }

但事实并非如此:

template<typename T> struct Something
{ static constexpr const char str[] = "int"; };

int main()
{ std::cout << Something<int>::str << std::endl; }

gcc-4.8 说:"undefined reference to Something<int>::str".

在 class:

之外定义静态成员可以解决这个错误
template<typename T>
constexpr const char Something<T>::name[];

为什么不是指针而是数组?毕竟都是 static constexpr 成员。

N3485,§3.2 [basic.def.odr]/3 说:

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).

对于数组,它必须经过数组到指针的转换才能适应 operator<< 的重载集。这没有在上面的文本中列出,所以数组 str 是 odr-used。

§9.4.2 [class.static.data]/3 说(强调我的):

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.

由于数组 str 是 odr-used,它必须在 class 之外定义。指针 str 未被 ODR 使用,因此不必如此。

如果对象或函数被 ODR 使用,则必须对其进行定义。在某些情况下,对象和函数未被 ODR 使用,在这些情况下,您不必定义它们。无论静态 class 成员的声明是否具有初始值设定项,它仍然不是定义。在所有情况下,规则是如果静态成员是 odr-used,则需要在封闭的命名空间范围内进行超出 class 的定义。

直觉上 "odr-used" 表示 "the linker needs its address"。如果 constexpr 变量仅以需要其值的方式使用——而不是其地址——那么它可能会避免被 odr-used。在这种情况下,编译器只是内联它的值,不需要定义它,因为链接器不需要它的地址。 const char* Something<int>::str 是这种情况,但 const char Something<int>::str[] 不是。

"But they're the same!",你喊。不是这样。因为当 strconst char* 时,它的 value 是字符串文字 "int"address。需要字符串文字的地址,但不需要 str 本身的地址。前者为str的值,满足被odr-used的要求;编译器可以直接内联它。但是当 strconst char[] 时,它的 value 是字符串文字 "int" 本身。当您尝试使用 istream::operator<< 输出它时,它会隐式转换为 const char*。而const char*是字符串字面量的地址,即Something<int>::str的地址。因此在这种情况下 Something<int>::str 是 odr-used;需要它的地址。

标准中有逻辑可用于精确确定何时使用变量 ([basic.def.odr])。但我不打算引用它,因为它是整个标准中最令人困惑的部分。我会说,在你有 const char* Something<int>::str 的情况下,立即应用左值到右值的转换,这是不使用 odr 的条件之一;在你有 const char Something<int>::str[] 的情况下,立即应用数组到指针的转换,这不满足条件。