类内 static const 声明为何可与类外 inline constexpr 定义配合使用?
这种写法是合法的(well-formed)。类内的 static const 声明和类外的 inline constexpr 定义指向同一个静态数据成员,对象类型也一致。对于变量,标准允许只在定义中使用 constexpr,此前的声明可以不带 constexpr。
这里将声明和定义分开,还有一个原因:声明这些成员时,partial_ordering 自身仍是不完整类型。
为什么 const 声明能对应 constexpr 定义
以 less 为例:
// 类内声明
static const partial_ordering less;
// 类外定义
inline constexpr partial_ordering partial_ordering::less(ord::less);
类内声明确定了两件事:
less是静态数据成员。- 它的类型是
const partial_ordering。
类外定义中的 constexpr 也会使对象具有 const 限定,所以定义的对象类型仍是 const partial_ordering,两者没有类型冲突。constexpr 还对类型和初始化提出了额外要求,包括对象必须具有 literal type,并满足常量初始化要求。参见标准草案的 [dcl.constexpr]。
按照静态数据成员的定义语法,类外定义不再写 static;partial_ordering::less 已经指明了要定义的成员。
为什么不要求每次声明都写 constexpr
对于变量,constexpr 只能用于定义。类内这行代码只是声明:
static const partial_ordering less;
因此,可以在这里省略 constexpr,等到类外提供初始化器、定义对象时再使用。
“只要某次声明使用了 constexpr,所有声明就都必须使用它”的要求适用于函数和函数模板,不适用于这里的变量。两项规则都见 [dcl.constexpr]第 1 段。
为什么不能直接合并到类内
题目中的“等价”有一个前提:合并后的类内定义本身必须合法。
类内的非 inline 静态数据成员声明不是定义,因此标准允许它使用不完整类型。下面的写法是合法的:
struct Value {
static const Value zero;
};
声明 zero 时,Value 尚未完整,但此处只需要声明成员。参见 [class.static.data]第 3 段。
如果改为类内定义:
struct Value {
inline static constexpr Value zero{}; // 错误:Value 尚未完整
};
这里需要定义一个 Value 对象,而此时 Value 仍是不完整类型,因此不合法。
partial_ordering 的这些常量也具有所属类自身的类型,所以需要在类内声明,等类定义完成后再定义。
自定义同类常量时怎样写
可以按以下顺序编写:
- 在类内用
static const声明成员。 - 在类定义结束后,用
inline constexpr提供定义和初始化器。 - 将需要读取常量值的常量表达式放在该定义之后。
下面的示例可用于 C++17 及以后版本:
struct Ordering {
int value;
constexpr explicit Ordering(int v) noexcept : value(v) {}
static const Ordering less;
static const Ordering equivalent;
};
inline constexpr Ordering Ordering::less{-1};
inline constexpr Ordering Ordering::equivalent{0};
static_assert(Ordering::less.value == -1);
static_assert(Ordering::equivalent.value == 0);
定义静态成员时,Ordering 已经是完整类型,初始化所调用的构造函数也已有定义,满足这里的常量求值要求。
inline 和定义位置有哪些注意事项
在满足单一定义规则(ODR)的条件下,inline 允许这类变量的定义出现在多个翻译单元中,同时仍表示同一个实体。因此,通常会将类定义和紧随其后的 inline constexpr 定义一起放在头文件中。参见 [dcl.inline]。
这里显式写出的 inline 有实际作用。按照当前草案及 CWG 2531 的修正,静态数据成员只有在首次声明带有 constexpr 时,才会隐式成为 inline 变量。题目中的首次声明只有 static const,因此应保留类外定义中的 inline。
类内的 static const 声明本身也不足以让编译器读取这个类类型对象的常量值。例如,用上述 Ordering::less.value 做 static_assert 时,必须能访问到带初始化器的定义。稍后出现的 constexpr 定义不会让此前的读取自动成为合法的常量表达式。相关要求见 [expr.const.init]。
备注:内容仅供参考。
