Static Constexpr vs Inline Constexpr in C++

Understanding the difference between static constexpr and inline constexpr member variables in C++ primarily depends on the C++ standard version being used. In modern C++ (C++17 and later), any static constexpr member variable is implicitly inline, making the two forms functionally identical for member variables. However, historically and semantically, the inline specifier addresses linkage and the One Definition Rule (ODR), resolving limitations that previously required out-of-line definitions for static constexpr members.

The Pre-C++17 Problem: ODR-Use of static constexpr

Prior to C++17, declaring a static constexpr variable inside a class only served as an in-class declaration:

struct Config {
    static constexpr int MaxRetries = 3; // Declaration with initializer
};

While you could use Config::MaxRetries in compile-time contexts (such as array bounds or template arguments), problems arose when the variable was "ODR-used" (One Definition Rule used). An ODR-use occurs when you take the variable's address or bind it to a non-inlined reference:

void logValue(const int& val);

logValue(Config::MaxRetries); // ODR-use: causes undefined reference linker error in C++11/C++14

To resolve this in C++11 and C++14, programmers were required to provide an out-of-line definition in a single implementation file (.cpp):

constexpr int Config::MaxRetries; // Out-of-line definition

The C++17 Solution: Inline Variables

C++17 introduced the inline specifier for variables. An inline variable can be defined in multiple translation units (such as via a shared header file) without violating the One Definition Rule, as the linker merges them into a single object.

Alongside this feature, the C++17 standard updated the rules for static constexpr member variables: any static constexpr member variable is now implicitly an inline variable.

Because of this rule, the following declarations in C++17 and later are completely equivalent:

struct Config {
    static constexpr int ValueA = 10;        // Implicitly inline in C++17+
    static inline constexpr int ValueB = 10; // Explicitly inline (redundant)
};

In modern C++, both ValueA and ValueB can have their addresses taken or be passed by reference from any translation unit without needing a separate out-of-line definition in a .cpp file.

Can Non-Static Member Variables Be constexpr?

A non-static data member cannot be declared constexpr. A constexpr variable must have a value that is constant and known at compile time. Since non-static members are instantiated per-object at runtime, declaring a non-static member variable as constexpr or inline constexpr is a compilation error.

What About Member Functions?

For member functions, the distinction is also resolved implicitly:

  • Any function declared constexpr is automatically considered inline.
  • Explicitly writing inline constexpr on a member function is valid, but the inline keyword is redundant.

Key Takeaways

  • In C++11 and C++14: static constexpr data members are not inline. If they are ODR-used (e.g., passed by const reference), an out-of-line definition in a separate .cpp file is required to avoid linker errors.
  • In C++17 and Later: static constexpr member variables are implicitly inline. Adding the inline keyword (static inline constexpr) is valid but redundant.
  • Non-Static Members: Non-static member variables can never be constexpr.