10

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 the constexpr specifier is implicitly an inline function or variable (10.1.6). If any declaration of a function or function template has a constexpr specifier, then all its declarations shall contain the constexpr 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 as const. Such an object shall have literal type and shall be initialized. In any constexpr 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 constness, 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 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.

§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.

monkey0506
  • 2,489
  • 1
  • 21
  • 27
  • Tagged as both "c++11" and "c++17" because the behavior applies to both (except for the implicit inlining of C++17). Standards quotes are cited from C++17 paper N4659. – monkey0506 May 21 '18 at 00:44
  • 1
    Note that in C++17, when a static data member declaration within the class definition uses either `constexpr` or `inline`, the declaration within the class definition is a definition, and any redeclaration outside the class definition is not a definition: [basic.def]/(2.3) and (2.4). – aschepler May 21 '18 at 01:02
  • @aschepler yes, that citation is also relevant, though neither `constexpr` nor `inline` are applied to the code in question. Thanks. – monkey0506 May 21 '18 at 01:07
  • 1
    Related, but about C++11 specifically: https://stackoverflow.com/questions/11928089/ – aschepler May 21 '18 at 01:10
  • @aschepler I have come across several questions (that perhaps should be marked as duplicates) of a similar nature. Most of them have accepted answers that it isn't possible, though as I noted above, GCC, Clang, and MSVC all support this approach. – monkey0506 May 21 '18 at 01:17
  • @monkey_05_06, what do you mean by "*which cannot be initialized **in-class***"? – Joseph D. May 21 '18 at 01:27
  • @codekaizer a static member of its own class can only be initialized at namespace scope, because at the time the member is being declared (inside the class definition), the class itself is an incomplete type. – monkey0506 May 21 '18 at 01:28
  • [Richard Smith commented here](https://stackoverflow.com/questions/11928089/static-constexpr-member-of-same-type-as-class-being-defined/12558623#comment17063348_12558623) and cited C++11 §3.5/10, noting that the `constexpr` specifier is **not** part of a variable's type. The `constexpr` specifier does implicitly mean `const`, which is part of the variable's type, but `constexpr` itself is not. – monkey0506 May 21 '18 at 12:03
  • Sorry if this is clear to others, but doesn't §12.2.3.2 say that it would not be inline, even though `static constexpr` implies inline in c++17? – user975989 May 21 '18 at 18:30
  • @user975989 the portions of §12.2.3.2 I cited above do refer to static data members that aren't inline, but they aren't defining any rules about what is or isn't inline. This does define whether a static data member needs an *initializer* statement at class scope or namespace scope though, which is why I've included it. – monkey0506 May 21 '18 at 18:54
  • The title seems wrong, in your example the constexpr is NOT required on the definition of ZERO and in fact the question is whether it is optionally allowed or not – M.M May 23 '18 at 02:04
  • @M.M how exactly do you suppose that "is it optionally allowed" and "is it required" to not be effectively asking the same thing? The code example I provided was provided because that is the **only** way to make it work and compile *and* it *actually* works and compiles on GCC, Clang, and MSVC. There is no alternative code example that would be useful for anything other than illustrating that "this code **doesn't** actually work". – monkey0506 May 23 '18 at 02:43
  • @monkey0506 [example](https://godbolt.org/g/z4rHa3) , that was the normal way of doing things until recently. It is definitely not "required" to use `constexpr`. The discussion on the other question was about whether the addition of `constexpr` made the code illegal – M.M May 23 '18 at 03:07
  • @M.M you can attribute whatever meaning you want to my words, but both questions were raised by me, specifically to the end of making a `constexpr` static data member of its containing class. I never asked whether `constexpr` was required to define a static data member, so please leave your strawmen out of this discussion. There was never any "addition of `constexpr`", `constexpr` members were precisely the topic at hand. – monkey0506 May 23 '18 at 03:28
  • @M.M I edited the title since you seemingly took it as ambiguous or otherwise unclear. The question was always about `constexpr` static data members. – monkey0506 May 23 '18 at 03:31
  • What "strawmen" are you talking about? I'm just trying to understand your question – M.M May 23 '18 at 03:39
  • The argument you've made against my post was never relevant to the question I asked. I've updated the title, so there is absolutely no way that it could reasonably be seen as unclear or ambiguous. – monkey0506 May 23 '18 at 03:47

1 Answers1

1

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 if o is an object, such an initializer may also invoke constexpr constructors for o 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.

Joseph D.
  • 11,804
  • 3
  • 34
  • 67
  • "I believe your code is fine." Thanks, I believe so too. This question was mainly to open up the dialogue of whether the standards have a glaring defect by not requiring these declarations and definitions to match in `constexpr` usage. I don't believe the standards are defective (in this respect), but others do seem to believe they are. – monkey0506 May 21 '18 at 11:40