C++ 对静态constexpr char[]的未定义引用

C++ 对静态constexpr char[]的未定义引用,c++,c++11,static-members,constexpr,C++,C++11,Static Members,Constexpr,我想在我的类中有一个静态常量字符数组。GCC抱怨并告诉我应该使用constexpr,尽管现在它告诉我这是一个未定义的引用。如果我使数组成为非成员,那么它将编译。发生了什么事 // .hpp struct foo { void bar(); static constexpr char baz[] = "quz"; }; // .cpp void foo::bar() { std::string str(baz); // undefined reference to baz } 添加

我想在我的类中有一个
静态常量
字符
数组。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修复了
constepr static
成员变量的此问题,如果使用odr,则需要一个越界定义。有关C++17之前版本的详细信息,请参见此答案的后半部分

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

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

并修改了[basic.def]p2:

除非

  • 它在类定义之外声明了一个静态数据成员,并且该变量是在类中使用constexpr说明符定义的(不推荐使用此用法;请参见[depr.static_constexpr])

并加上:

<>与以前的C++国际标准兼容 静态数据成员可以在类外冗余地重新声明 没有初始值设定项。这种用法已被弃用。[ 例如:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)
 — 结束示例 ]


C++14及更早版本 在C++03中,我们只允许为常量积分或常量枚举类型提供类内初始值设定项,在C++11中,使用
constexpr
将其扩展到文本类型

在C++11中,我们不需要为静态
constepr
成员提供名称空间范围定义,如果它没有使用odr,我们可以从草案C++11标准部分
9.4.2
[class.static.data]中看到这一点,其中说(重点放在后面):

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

那么问题就变成了,
baz
odr是否在这里使用:

答案是肯定的,因此我们还需要一个名称空间范围定义

那么,我们如何确定是否使用了odr变量呢?
3.2节[basic.def.odr]中最初的C++11措辞说:

除非表达式是未计算的表达式,否则可能会对其进行计算 操作数(第5条)或其子表达式。名称为 除非 它是一个满足出现在场景中的要求的对象 常量表达式(5.19)和左值到右值的转换 (4.1)立即应用

因此,
baz
确实会产生一个常量表达式,但由于
baz
是一个数组,因此不会立即应用左值到右值的转换。这在第
4.1节
[conv.lval]中有介绍,其中说:

非函数、非数组类型T的glvalue(3.10)可以是 转换为prvalue.53[…]

数组到指针转换中应用的内容


[basic.def.odr]的这一措辞由于以下原因而发生了变化,因为该措辞未涵盖某些情况,但这些变化不会改变这种情况的结果。

字符[]
更改为:

static constexpr char * baz = "quz";

通过这种方式,我们可以在一行代码中使用定义/声明/初始值设定项。

这确实是C++11中的一个缺陷-正如其他人所解释的,在C++11中,静态constexpr成员变量与其他类型的constexpr全局变量不同,具有外部链接,因此必须在某个地方显式定义

还值得注意的是,在使用优化编译时,您通常可以在实践中避开没有定义的静态constexpr成员变量,因为它们最终可能在所有用途中都是内联的,但是如果您在没有优化的情况下编译,您的程序通常将无法链接。这使得这成为一个非常常见的隐藏陷阱-您的程序编译fin使用优化,但一旦关闭优化(可能是为了调试),它就无法链接


好消息是——这个缺陷在C++17中得到了修复!不过这种方法有点复杂:在C++17中,静态constepr成员变量。have在C++17中是一个新概念,但实际上意味着它们不需要任何明确的定义。

我的静态成员外部链接解决方法是使用
constepr
引用成员getter(不会遇到@gnzlbg作为对@deddebme答案的注释而提出的问题)。
这个习惯用法对我来说很重要,因为我讨厌在我的项目中有多个.cpp文件,并尝试将数量限制为一个,它只包含
\include
s和
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 vesion是5.4.0。添加“-O2”可以修复此编译错误
// 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]'