我想在我的类中有一个静态const char数组。GCC抱怨并告诉我应该使用constexpr,尽管现在它告诉我它是一个未定义的引用。如果我把数组设为非成员,它就会编译。这是怎么回事?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

添加到您的cpp文件:

constexpr char foo::baz[];

原因:你必须提供静态成员的定义和声明。声明和初始化式放在类定义中,但成员定义必须分开。


c++ 17引入了内联变量

c++ 17修复了这个问题,如果使用奇数,则constexpr静态成员变量需要出行定义。关于c++ 17之前的细节,请参阅这个答案的后半部分。

提案P0386内联变量引入了将内联说明符应用于变量的能力。特别是在这种情况下,constexpr意味着静态成员变量的内联。提案说:

内联说明符可以应用于变量,也可以应用于函数。声明的变量 Inline与声明为Inline的函数具有相同的语义:它可以定义为in 必须在使用odr的每个翻译单元中定义多个翻译单元 程序的行为就像只有一个变量一样。

并修改[basic.def]p2:

声明就是定义,除非 ... 它在类定义之外声明一个静态数据成员,并且变量是在类中使用constexpr说明符定义的(这种用法已弃用;参见[depr.static_constexpr]), ...

添加[depr.static_constexpr]:

为了与以前的c++国际标准兼容,使用了一个constexpr 静态数据成员可以在类外部冗余地重新声明 没有初始化式。不建议使用这种用法。(例子: 结构A { Static constexpr int n = 5;//定义(c++ 2014中的声明) }; A::n;//冗余声明(c++ 2014定义) - end示例]


c++ 14及更早的版本

在c++ 03中,我们只允许为const积分或const枚举类型提供类内初始化式,在c++ 11中使用constexpr将其扩展为文字类型。

在c++ 11中,如果静态constexpr成员不是odr使用的,我们不需要为它提供命名空间作用域定义,我们可以从c++ 11标准草案第9.4.2节[class.static. static]中看到这一点。它说(强调我的未来):

[...]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.

那么问题来了,这里用的是baz奇数吗?

std::string str(baz); 

答案是肯定的,因此我们还需要一个名称空间作用域定义。

那么我们如何确定一个变量是否是奇数使用的呢?c++ 11第3.2节[basic.def.odr]中的原始措辞是这样的:

除非表达式为未求值表达式,否则它是潜在求值表达式 操作数(第5条)或其子表达式。变量的名称 作为一个潜在求值表达式出现是奇数使用除非 对象是满足出现在对象中的要求的对象 常量表达式(5.19)和左值到右值的转换 (4.1)立即生效。

因此,baz确实产生了一个常量表达式,但左值到右值的转换不会立即应用,因为它不适用,因为baz是一个数组。这在第4.1节[convl .lval]中提到:

非函数、非数组类型T的glvalue(3.10)可以为 转换为右值。53[…]

在数组到指针的转换中应用了什么。

由于缺陷报告712,更改了[basic.def.odr]的措辞,因为有些情况没有包含在这个措辞中,但这些更改不会改变这个情况的结果。


更优雅的解决方案不是将char[]更改为:

static constexpr char * baz = "quz";

这样我们就可以在一行代码中包含定义/声明/初始化式。


这实际上是c++ 11中的一个缺陷——正如其他人解释的那样,在c++ 11中,静态的constexpr成员变量与其他类型的constexpr全局变量不同,它具有外部链接,因此必须在某处显式地定义。

同样值得注意的是,在使用优化进行编译时,您通常可以在实践中使用没有定义的静态constexpr成员变量,因为它们可能在所有使用中都内联,但如果您在没有优化的情况下编译,您的程序通常会链接失败。这使得这成为一个非常常见的隐藏陷阱——您的程序在优化下编译良好,但一旦您关闭优化(可能是为了调试),它就无法链接。

好消息是——这个缺陷在c++ 17中被修复了!这种方法有点复杂:在c++ 17中,静态constexpr成员变量是隐式内联的。将内联应用于变量是c++ 17中的一个新概念,但这实际上意味着它们不需要在任何地方显式定义。


对于静态成员的外部链接,我的解决方法是使用constexpr引用成员getter(这不会遇到@gnzlbg作为来自@deddebme的答案的注释提出的问题)。 这个习惯用法对我来说很重要,因为我讨厌在我的项目中有多个.cpp文件,并试图将数量限制为一个,其中除了#includes和一个main()函数外什么都没有。

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'

在我的环境中,gcc版本是5.4.0。添加“-O2”可以修复此编译错误。在要求优化时,gcc似乎可以处理这种情况。