C++17引入了内联变量
对于constexpr static
成员变量,C++17修复了需要在odr-used时进行外部定义的问题。有关C++17之前的详细信息,请参见此答案的后半部分。
提案P0386 Inline Variables引入了将inline
限定符应用于变量的能力。特别是对于这种情况,constexpr
意味着静态成员变量是inline
的。该提案说:
内联限定符也可以应用于变量以及函数。声明为内联的变量具有与声明为内联的函数相同的语义:它可以在多个翻译单元中定义,必须在使用它的每个翻译单元中定义,并且程序的行为就像存在一个变量一样。
并修改[basic.def]p2:
一个声明就是一个定义,除非
- 它在类定义之外声明了一个静态数据成员,并且该变量在类内部使用constexpr限定符进行了定义(此用法已被弃用,请参见[depr.static_constexpr]),
...
并添加
[depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr
static data member may be redundantly redeclared outside the class
with no initializer. This usage is deprecated. [ Example:
struct A {
static constexpr int n = 5;
};
constexpr int A::n;
— end example ]
C++14及之前版本
在C++03中,我们只能为const integrals或const enumeration types提供类内初始值设定项,在C++11中使用constexpr
将此扩展到literal types。
在C++11中,如果静态constexpr
成员没有被odr-used,则无需为其提供命名空间范围定义。这可以从C++11标准草案的第9.4.2
节[class.static.data]中看出(下文将强调我的重点):
[...] 可以在类定义中使用constexpr指定文字类型的静态数据成员;如果是这样,则其声明应指定一个大括号或等于初始化程序,在其中每个初始化项都是常量表达式的赋值表达式。[注:在这两种情况下,该成员可以出现在常量表达式中。——end note]
如果程序中使用了静态成员且该成员是odr-used(3.2),则仍然需要在命名空间范围内定义该成员,但该定义不应包含初始化项。
那么问题就变成了,这里是否 baz
被 odr-used:
std::string str(baz)
答案是是,因此我们还需要一个命名空间范围定义。
那么如何确定变量是否odr-used?原始的C++11措辞在第3.2
节[basic.def.odr]中说:
除非它是未评估的操作数(第5条),或其子表达式,否则表达式是可能被评估的。出现为可能被评估的表达式的变量是odr-used,除非它是一个满足出现在常量表达式中的要求的对象(5.19),并且立即应用lvalue-to-rvalue转换(4.1)。
因此,baz
确实产生了常量表达式,但由于baz
是一个数组,因此lvalue-to-rvalue转换不适用,因此没有立即应用。这在第4.1
节[conv.lval]中有所涵盖,其中写道:
一个非函数、
非数组类型 T 的 glvalue(3.10)可以被转换为 prvalue.53[...].
在
array-to-pointer conversion 中应用了什么。
由于
Defect Report 712,
[basic.def.odr] 的措辞已更改,因为这些措辞没有涵盖某些情况,但这些更改不会改变此情况的结果。