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

Pub*_*bby 168 c++ static-members constexpr c++11

我想static const char在班上有一个数组.海湾合作委员会抱怨并告诉我应该使用constexpr,虽然现在它告诉我这是一个未定义的参考.如果我使数组成为非成员,那么它将编译.到底是怎么回事?

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

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Run Code Online (Sandbox Code Playgroud)

Ker*_* SB 167

添加到您的cpp文件:

constexpr char foo::baz[];
Run Code Online (Sandbox Code Playgroud)

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

  • 这看起来很奇怪......因为它似乎没有为编译器提供它以前没有的一些信息...... (64认同)
  • 当您在.cpp文件中进行类声明时,它看起来更奇怪!您在类声明中初始化该字段,但您仍需要通过在类下面编写constexpr char foo :: baz []来"_declare_"该字段.看来使用constexpr的程序员可以按照一个奇怪的提示编译他们的程序:再次声明它. (28认同)
  • @LukaszCzerwinski:你要找的词是"定义". (5认同)
  • 如果foo被模板化,表达式会是什么样子?谢谢. (5认同)
  • 没错,没有新信息:使用`decltype(foo :: baz)constexpr foo :: baz声明; (4认同)
  • @NeilKirk:这个讨论变得非常困惑.`constexpr`是一个不同的,无关的东西,没有立即与联系相关联. (3认同)

Sha*_*our 72

C++ 17引入了内联变量

C++ 17修复了constexpr静态成员变量的这个问题,如果它是ord-used则需要一个out-line定义.有关预C++ 17的详细信息,请参阅下面的原始答案.

Proposal P0386 Inline Variables引入了将内联说明符应用于变量的功能.特别是对于这种情况,constexpr意味着内联静态成员变量.提案说:

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

和修改[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)
Run Code Online (Sandbox Code Playgroud)

 - 结束例子]

原始答案

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

在C++ 11中,我们不需要为静态constexpr成员提供命名空间作用域定义,如果它没有使用,我们可以从草案C++ 11标准部分constexpr static [class.static.data]中看到这一点.(强调我的前进):

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

那么问题就变成了,这里inline 使用的odr:

std::string str(baz); 
Run Code Online (Sandbox Code Playgroud)

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

那么我们如何确定变量是否使用了constexpr [basic.def.odr]部分中的原始C++ 11措辞说:

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

因此,inline会产生一个常量表达式,但不会立即应用左值到右值的转换,因为由于它是constexpr一个数组而不适用.这将在constexpr [conv.lval]部分中介绍,其中说:

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

什么应用于数组到指针的转换.

由于缺陷报告712,[basic.def.odr]的措辞发生了变化,因为有些案例没有涵盖这一措辞,但这些变化并未改变本案的结果.


小智 34

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

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

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

  • 适用于C++ 17信息.您可以将此信息添加到已接受的答案! (4认同)

ded*_*bme 5

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

static constexpr char * baz = "quz";
Run Code Online (Sandbox Code Playgroud)

这样,我们可以在1行代码中包含定义/声明/初始化器。

  • 使用`char []`可以使用`sizeof`在编译时获取字符串的长度,使用`char *`则不能(在这种情况下,它将返回指针类型的宽度,为1)。 (9认同)
  • 如果要严格使用ISO C ++ 11,这也会生成警告。 (2认同)

Jos*_*fer 5

我对静态成员的外部链接的解决方法是使用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]'
Run Code Online (Sandbox Code Playgroud)