constexpr静态成员变量的异常行为

Strange behavior with constexpr static member variable

本文关键字:异常 静态成员 变量 constexpr      更新时间:2023-10-16

这是对静态constexpr char[]的未定义引用的后续问题。

以下程序构建并运行良好。

#include <iostream>
struct A {
   constexpr static char dict[] = "test";
   void print() {
      std::cout << A::dict[0] << std::endl;
   }
};
int main() {
   A a;
   a.print();
   return 0;
}

但是,如果我将A::print()更改为:

   void print() {
      std::cout << A::dict << std::endl;
   }

我在g++4.8.2中得到以下链接器错误。

/tmp/cczmF84A.o:在函数"A::print()"中:socc.cc:(.text._ZN1A5printEv[_ZN1A5 printEv]+0xd):对"A::dict"的未定义引用collect2:错误:ld返回1退出状态

链接器错误可以通过添加一行来解决:

constexpr char A::dict[];

在类定义之外。

但是,我不清楚为什么使用数组的一个成员不会导致链接器错误,而使用数组会导致链接器出错。

该标准不要求对故障进行任何诊断,以便在需要定义的地方提供定义。

3.2一个定义规则[basic.def.odr]

4每个程序应包含该程序中使用的每个非内联函数或变量的一个定义;无需诊断。[…]

这意味着实现可以优化对此类变量的访问,这就是GCC的第一个案例中发生的情况。

GCC和clang都决定,他们更喜欢一致的用户体验,其中关于缺少定义的错误消息不取决于优化级别。通常,这意味着任何缺少的定义都会导致一条错误消息。然而,在这种情况下,GCC即使在-O0也在进行一些最小的优化,从而避免了错误。

但无论哪种方式,该程序都是错误的,因为即使是A::dict[0]也是ODR使用:

3.2一个定义规则[basic.def.odr]

3名称显示为潜在求值表达式ex的变量xex使用,除非将左值到右值转换(4.1)应用于x产生不调用任何非平凡函数的常量表达式(5.19),并且如果x是对象,则ex是表达式e的潜在结果集的元素,其中左值到右值的转换(4.1)应用于e,或者e是丢弃的值表达式(第5条)。[…]

A::dict的使用不涉及左值到右值的转换,它涉及数组到指针的转换,因此该异常不适用。

除了@hvd在他的回答中提供的信息。。。

来自C++标准草案N3337(强调矿):

9.4.2静态数据成员

3如果非易失性const static数据成员是整型或枚举型,则其在类定义中的声明可以指定大括号或相等初始化器,其中作为赋值-表达式的每个初始化器子句都是常量表达式(5.19)。文本类型的static数据成员可以在类定义中将constexpr说明符声明;如果是,则其声明应指定大括号或相等的初始值设定项,其中作为赋值表达式的每个初始值设定子句都是常量表达式。[注意:在这两种情况下,成员都可能出现在常量表达式中。--结束注释]如果在程序中使用odr(3.2),则成员仍应在命名空间范围中定义,并且命名空间范围定义不应包含初始值设定项

假定A::data是表达式A::data[0]中使用的odr,根据标准,它应该在名称空间范围中定义。g++能够成功创建一个程序,而不需要在名称空间范围中定义A::data,这一事实并不能使程序正确。为了符合标准,应定义A::data