222

我想static 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
}
4

6 回答 6

222

添加到您的 cpp 文件中:

constexpr char foo::baz[];

原因:您必须提供静态成员的定义以及声明。声明和初始化器位于类定义中,但成员定义必须分开。

于 2011-11-04T23:22:03.210 回答
111

C++17 引入内联变量

C++17 修复constexpr static了在 odr 使用时需要离线定义的成员变量的这个问题。有关 C++17 之前的详细信息,请参阅此答案的后半部分。

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

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

并修改了[basic.def]p2:

声明是一个定义,除非
...

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

...

并添加[depr.static_constexpr]

为了与以前的 C++ 国际标准兼容,constexpr 静态数据成员可以在没有初始化器的类外部冗余地重新声明。此用法已弃用。[ 例子:

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 中,我们只被允许为const 整数const 枚举类型提供类内初始化器,在 C++11 中,使用constexpr它被扩展为文字类型

constexpr在 C++11 中,如果静态成员不是odr-used ,我们不需要为它提供命名空间范围定义,我们可以从草案 C++11 标准部分9.4.2 [class.static.data]中看到这一点,它说(强调我的未来):

[...]可以使用 constexpr 说明符在类定义中声明文字类型的静态数据成员;如果是这样,它的声明应指定一个大括号或等式初始化器,其中作为赋值表达式的每个初始化器子句都是一个常量表达式。[ 注意:在这两种情况下,成员都可能出现在常量表达式中。—尾注] 如果成员在程序中被 odr-used (3.2) 并且命名空间范围定义不应包含初始化程序,则该成员仍应在命名空间范围内定义。

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

std::string str(baz); 

答案是肯定的,所以我们也需要一个命名空间范围定义。

那么我们如何确定一个变量是否被odr-used呢?3.2 [basic.def.odr]部分中的原始 C++11 措辞说:

一个表达式可能被求值,除非它是一个未求值的操作数(第 5 条)或其子表达式。名称显示为潜在求值表达式的变量是 odr-used 的,除非 它是满足出现在常量表达式(5.19)中的要求并且立即应用左值到右值转换 (4.1) 的对象

baz确实会产生一个常量表达式,但不会立即应用左值到右值的转换,因为它是baz数组而不适用。这在4.1 [conv.lval]部分中有介绍,它说:

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

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

[basic.def.odr]的措辞因缺陷报告 712而有所更改,因为此措辞未涵盖某些情况,但这些更改不会更改此案例的结果。

于 2015-03-04T04:19:49.447 回答
47

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

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

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

于 2017-10-12T22:02:35.753 回答
5

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

static constexpr char * baz = "quz";

这样我们可以在 1 行代码中拥有定义/声明/初始化程序。

于 2016-04-19T14:40:22.100 回答
5

我对静态成员的外部链接的解决方法是使用constexpr引用成员获取器(这不会遇到问题@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]'
于 2018-08-30T11:49:46.380 回答
-1

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

于 2019-04-29T22:40:59.730 回答