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而有所更改,因为此措辞未涵盖某些情况,但这些更改不会更改此案例的结果。