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

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

本文关键字:指针 一个 constexpr 静态 字符串 引用 未定义      更新时间:2023-10-16

这项工作:

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说:"对Something<int>::str的未定义引用"。

定义类外部的静态成员可以解决此错误:

template<typename T>
constexpr const char Something<T>::name[];

为什么它不是指针而需要数组?毕竟,两人都是static constexpr成员。

如果对象

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

直觉是"odr-used"意味着"链接器需要它的地址"。如果一个constexpr变量只以需要其值的方式使用---而不是它的地址---那么它可以避免被 odr 使用。在这种情况下,编译器只是内联其值,不需要定义它,因为链接器不需要它的地址。const char* Something<int>::str是这样,但不是const char Something<int>::str[]

"但他们是一样的!",你喊道。并非如此。因为当strconst char*时,它的值是字符串文字"int"地址。需要字符串文本的地址,但不需要str本身的地址。前者是str的值,它满足被odr使用的要求;编译器可以内联它。但是当str是一个const char[]时,它的值是字符串文字"int"本身。当你尝试使用 istream::operator<< 输出它时,它被隐式转换为const char*。但是const char*的值是字符串文字的地址,即Something<int>::str的地址。因此,在这种情况下,Something<int>::str是使用 odr;它的地址是必需的。

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

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

名称显示为潜在计算表达式的变量 x EX 是 ODR 使用的,除非 X 是满足要求的对象 用于出现在常量表达式 (5.19) 中,ex 是 表达式 E 的潜在结果集,其中 左值到右值转换 (4.1) 应用于 e,或 e 是 丢弃值表达式(第 5 条)。

对于数组,它必须进行数组到指针的转换,以适合operator<<的重载集。这在上面的文本中没有列出,因此数组str是 odr 使用的。

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

可以在类中声明文本类型的静态数据成员 使用constexpr说明符进行定义;如果是这样,其声明应 指定一个大括号或等于初始值设定项,其中每个初始值设定项子句 即赋值表达式是一个常量表达式。[ 注:在 在这两种情况下,成员都可能出现在常量表达式中。— 完 注意 ] 如果成员是 ODR 在程序中使用的 (3.2) 和命名空间范围定义应 不包含初始值设定项。

由于数组str是 odr 使用的,因此必须在类外部定义它。指针str不是 odr 使用的,因此不必使用。