示例: 在头文件中:
class Foo
{
static const int IntArray[];
};
在源文件中:
constexpr int Foo::IntArray[] = { 1, 2, 3, 4 };
这在 g++ 上编译,并允许我将初始值设定项列表放在源文件中而不是标头中。(如果标头中是 constexpr,编译器需要在标头中立即初始化)。同时仍然允许将数组用于 constexpr 评估......
这是有效的便携式C++吗?
正确的方式
在我们开始语言律师之前,正确的方法是反过来做。 在头文件中:
class Foo
{
static constexpr int IntArray[] = { 1, 2, 3, 4 };
};
然后在源文件中:
constexpr int Foo::IntArray[];
如果在类定义中声明static constexpr
类数据成员,则必须在当时和那里对其进行初始化。 这对于static const
数据成员是可选的。 如果在程序中的任何位置使用static constexpr
数据成员,则必须在一个源文件中给出类似于上面的定义,不带初始值设定项。
(草案)标准的内容
问题中的示例代码风格不好,显然至少有一个编译器拒绝它,但实际上它似乎符合 C++14 草案标准。 [DCL/ConstexPR] 说:
constexpr
说明符应仅适用于变量或变量模板的定义、函数或函数模板的声明或文本类型的static
数据成员的声明。 如果函数、函数模板或变量模板的任何声明具有constexpr
说明符,则其所有声明都应包含constexpr
说明符。
由于省略,其声明并非全部需要包含constexpr
说明符的通知。
在同一节的后面部分:
对象声明中使用的
constexpr
说明符将对象声明为const
。此类对象应具有文本类型,并应进行初始化。[...]
但另请参阅[class.static.data]:
如果非
volatile
const
static
数据成员是整型或枚举类型,则其在类定义中的声明可以指定大括号或等于初始值设定项,其中作为赋值表达式的每个初始值设定项子句都是常量表达式。可以使用constexpr
说明符在类定义中声明文本类型的static
数据成员;如果是这样,则其声明应指定一个大括号或等于初始值设定项,其中作为赋值表达式的每个初始值设定项子句都是常量表达式。[注意:在这两种情况下,成员都可能出现在常量表达式中。如果在程序中使用 odr 时,仍应在命名空间作用域中定义该成员,并且命名空间作用域定义不应包含初始值设定项。
在这种情况下,"odr-used"中的 odr 代表一个定义规则,意思是"其名称显示为潜在评估的表达式"。([basic.def.odr]) 最后一句话的意思是,如果你在类定义中声明static constexpr int foo = 0;
,并且稍后将在表达式中使用它,例如int x = MyClass::foo;
,那么只有一个源文件需要有一行类似constexpr int MyClass::foo;
,所以链接器知道要把它放在哪个对象文件中。
我怀疑它是否合规。声明和定义必须是相同的 AFAIK。
它当然不是便携式的。尽管 gcc、clang 和 Microsoft cl 2017 接受它,
国际商会报告:
<source>(6): error: member "Foo::IntArray" (declared at line 3) was previously not declared constexpr
constexpr int Foo::IntArray[] = { 1, 2, 3, 4 };
^
compilation aborted for <source> (code 2)
Compiler exited with result code 2