PAYATHON 2026

类内 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 的这些常量也具有所属类自身的类型,所以需要在类内声明,等类定义完成后再定义。

自定义同类常量时怎样写

可以按以下顺序编写:

  1. 在类内用 static const 声明成员。
  2. 在类定义结束后,用 inline constexpr 提供定义和初始化器。
  3. 将需要读取常量值的常量表达式放在该定义之后。

下面的示例可用于 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]。

备注:内容仅供参考。