Before C++17, you needed to re-declare all static
variables outside the class in exactly one translation unit (typically each translation unit is a .cpp
file and vice versa, but this isn’t required). As you pointed out, C++17 introduces inline
class member variables, and static constexpr
variables automatically qualify. You are not allowed to redeclare inline
variables outside the class, as you saw in your second example, but an exception was made for constexpr
because previously you were allowed (and in fact required) to do so, but the syntax is deprecated.
In [class.static.data]p2, it allows that syntax for non-inline members (“The declaration of a non-inline static data member in its class definition is not a definition and may be of
an incomplete type other than cv void. The definition for a static data member that is not defined inline
in the class definition shall appear in a namespace scope enclosing the member’s class definition.”)
In the next paragraph, the standard allows constexpr
outside-of-class declarations and requires them for non-constexpr
data (emphasis added):
If a non-volatile non-inline const
static data member is of integral or enumeration type, its declaration
in the class definition can specify a brace-or-equal-initializer in
which every initializer-clause that is an assignment-expression is a
constant expression (8.20). The member shall still be defined in a
namespace scope if it is odr-used (6.2) in the program and the
namespace scope definition shall not contain an initializer. An inline
static data member may be defined in the class definition
and may specify a brace-or-equal-initializer. If the member is
declared with the constexpr
specifier, it may be redeclared in
namespace scope with no initializer (this usage is deprecated; see
D.1). Declarations of other static data members shall not specify a
brace-or-equal-initializer.
And here’s the deprecation note, D.1 Redeclaration of static constexpr data members [depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the
class with no initializer. This usage is deprecated. [ Example:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
— end example ]