C++17 §10.1.5/1 states:
The
constexpr
specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. A function or static data member declared with theconstexpr
specifier is implicitly an inline function or variable (10.1.6). If any declaration of a function or function template has aconstexpr
specifier, then all its declarations shall contain theconstexpr
specifier.
A similar paragraph has existed in the standard since C++11 (§7.1.5/1), which is cited in a comment by Richard Smith, in which he contends that the C++ Standard does not require the constexpr
specifier to match between the declaration and definition of a variable. The last statement of the above paragraph explicitly requires the constexpr
specifier to match across function and function template declarations, but does not mention variable declarations.
§10.1.5/9 states:
A
constexpr
specifier used in an object declaration declares the object asconst
. Such an object shall have literal type and shall be initialized. In anyconstexpr
variable declaration, the full-expression of the initialization shall be a constant expression (8.20).
Of course if we have a separate declaration and definition, they will both need to match in const
ness, regardless of whether the constexpr
specifiers are required to match.
§12.2.3.2/2-3 says:
2 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 definition at namespace scope, the name of the static data member shall be qualified by its class name using the :: operator. The initializer expression in the definition of a static data member is in the scope of its class (6.3.7).3 If a non-volatile non-inline
const
static data member is of integral or enumeration type... If the member is declared with theconstexpr
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.
§D.1/1 reads:
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.
From which we can gather that if the member is declared with the constexpr
specifier, then a namespace scope definition is redundant and the initializer expression must be paired with the declaration and must be omitted from the definition/redeclaration.
To serve as a complete example, I offer up the case of a static member of its own literal type class (which cannot be initialized in-class):
struct S
{
static S const ZERO; // not marked `constexpr`, but still `const`
constexpr S(int value = {}) : _value{ value } {}
int const _value;
};
constexpr S S::ZERO{ 0 }; // implicitly `inline` (if C++17) and `const`
This interpretation of constexpr
use with static data members is supported by GCC, Clang, and MSVC, though I have been told that this is wrong.
Is it a violation to have non-matching use of the constexpr
specifier across variable declarations and definitions?
If this is in fact a violation, then it is impossible to correctly define a constexpr
static data member of its own class, as in-class definitions are prohibited because the type is incomplete and out-of-class definitions are prohibited from including an initializer if the in-class declaration is marked with the constexpr
specifier.
A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?
Actually, extern constexpr does make sense, and would be useful. In particular, static constexpr class member variables automatically have external linkage (which is a huge inconsistency and gotcha - people often forget to add a definition in a . cc file).
A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations. And when a value is computed at compile time instead of run time, it helps your program run faster and use less memory.
constexpr stands for constant expression and is used to specify that a variable or function can be used in a constant expression, an expression that can be evaluated at compile time. The key point of constexpr is that it can be executed at compile time.
A constexpr specifier used in an object declaration or non-static member function (until C++14) implies const. A constexpr specifier used in a function or static data member (since C++17) declaration implies inline. If any declaration of a function or function template has a constexpr specifier, then every declaration must contain that specifier.
A reference may be declared as constexpr when both these conditions are met: The referenced object is initialized by a constant expression, and any implicit conversions invoked during initialization are also constant expressions. All declarations of a constexpr variable or function must have the constexpr specifier.
Not initialized int j = 0; constexpr int k = j + 1; //Error! j not a constant expression A constexpr function is one whose return value is computable at compile time when consuming code requires it. Consuming code requires the return value at compile time to initialize a constexpr variable, or to provide a non-type template argument.
A constexpr function or constructor is implicitly inline. The following rules apply to constexpr functions: A constexpr function must accept and return only literal types. A constexpr function can be recursive. It can't be virtual. A constructor can't be defined as constexpr when the enclosing class has any virtual base classes.
If I were to read this:
static S const ZERO; // not marked `constexpr`, but still `const`
S::ZERO
will never change its value during run-time due to const
.
However:
constexpr S S::ZERO{ 0 }; // implicitly `inline` (if C++17) and `const`
Constant Evaluation
is done for S::ZERO
which will have constant integral value 0
for _value
.
This invokes your constexpr constructor
:
constexpr S(int value = {}) : _value{ value } {}
As per basic.start.static - Constant Initialization:
A constant initializer for a variable or temporary object
o
is an initializer whose full-expression is a constant expression, except that ifo
is an object, such an initializer may also invoke constexpr constructors foro
and its subobjects even if those objects are of non-literal class types.
AND expr.const/8.7 - Constant Evaluation:
a variable whose name appears as a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
Therefore:
Is it a violation to have non-matching use of the constexpr specifier across variable declarations and definitions?
I believe your code is fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With