Why Does Constexpr Static Member (Of Type Class) Require a Definition

Why does constexpr static member (of type class) require a definition?

The One Definition rule tells us that we can not have more than one definition of an odr-used variable in a program. So if a variable is odr-used then you need to define it but you can not define it the header file since it may be included more than once with the whole program. Odr-use violations do not require a diagnostic message and so you can violate this rule and the compiler is not obliged to notify you.

In your case you are indeed odr-using str_, and you can not include the definition in the header file because that would violate the one definiton rule since it can be included more than once within the program.

It is interesting to note that if you had done the following it would not have been odr-used:

return str_.size_;

You would therefore not need to define the variable, which can have some odd consequences in some examples. I doubt that really solves your problem long-term.

The odr rules are covered in the draft C++ standard section 3.2 and they say:

A variable x whose name appears as a potentially-evaluated expression
ex is odr-used unless applying the lvalue-to-rvalue conversion (4.1)
to x yields a constant expression (5.19) that does not invoke any
non-trivial functions and, if x is an object, ex is an element of the
set of potential results of an expression e, where either the
lvalue-to-rvalue conversion (4.1) is applied to e, or e is a
discarded-value expression (Clause 5). this is odr-used if it appears
as a potentially-evaluated expression (including as the result of the
implicit transformation in the body of a non-static member function
(9.3.1)).[...]

So str_ yield a constant expression, the lvalue-to-rvalue conversion is not applied the expression str_.size() and it is not a discarded value expression, so it is odr-used and therefore str_ is required to be defined.

On the other hand the lvalue-to-rvalue conversion is applied to the expression str_.size_, so it is not odr-used and does not require str_ to be defined.

c++11: Why a in-class initialization of a static constexpr not a definition?

Why a in-class initialization of a static constexpr not a definition?

Because of One Definition Rule (ODR). The rule says that there must be exactly one definition of each non-inline non-member and static member variable. Class definitions, due to their nature, are typically included into multiple translation units. If class definition contained a variable definition, then inclusion into multiple translation units would violate the ODR.

Since C++17, the language has inline variables, so you can define such inline variables within class definitions.

Should I always define a constexpr static data member outside of the class?

Should I always define a constexpr static data member outside of the class?

Always?! Only a Sith deals in absolutes.

C++17 made static constexpr variables implicitly inline (in the linkage sense, they always needed an in-class initializer). The out-of-class definition remains an optional but deprecated feature.

D.1 Redeclaration of static constexpr data members [depr.static_constexpr]

1 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 ]

It's still supported, so you don't have to go about updating all of your existing code bases today. But it may be removed in the future (three, six, maybe nine years from now?) So you won't be able to add a redundant declaration forever.

If you write a new piece of code that requires C++17 or newer, then there's no need to add this re-declaration out of some principle.

static constexpr member of same type as class being defined

If I interpret the Standard correctly, it isn't possible.

(§9.4.2/3) [...] A static data member of literal type can be declared in the
class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [...]

From the above (along with the fact that there is no separate statement about non-literal types in static data member declarations), I believe it follows that a static data member that is constexpr must be a literal type (as defined in §3.9/10), and it must have its definition included in the declaration. The latter condition could be satisfied by using the following code:

struct Foo {
constexpr Foo() {}
static constexpr Foo f {};
};

which is similar to your Attempt 1, but without the class-external definition.

However, since Foo is incomplete at the time of declaration/definition of the static member, the compiler can't check whether it is a literal type (as defined in §3.9/10), so it rejects the code.

Note that there is this post-C++-11 document (N3308) which discusses various problems of the current definition of constexpr in the Standard, and makes suggestions for amendments. Specifically, the "Proposed Wording" section suggests an amendment of §3.9/10 that implies the inclusion of incomplete types as one kind of literal type. If that amendment was to be accepted into a future version of the Standard, your problem would be solved.

Why does GCC think that the definition of a constexpr static data member must be marked constexpr?

This looks underspecified to me, I don't see an explicit requirement but we can see why it is an issue from defect report 699: Must constexpr member functions be defined in the class member-specification? which although dealing with constexpr member functions says the following (emphasis mine):

If the prohibition were relaxed to allow separate declaration and
definition of constexpr member functions, some questions would need to
be answered, such as whether the constexpr specifier must appear on
both declaration and definition (the inline specifier need not). If it
can be omitted in one or the other, there's a usability issue
regarding the fact that constexpr implies const; the const qualifier
would need to be specified explicitly in the declaration in which
constexpr was omitted.

Although in this case adding const does not solve the problem although in a simpler cases it does seem to solve the issue. We can see in a simpler case both clang and gcc require either const or constexpr:

struct T
{
static constexpr int blah = 1 ;
};

const int T::blah ;

Update

This gcc bug report: Bogus "error: redeclaration ... differs in ‘constexpr’" has the following quote from Richard Smith:

There is no rule requiring successive declarations of variables to
agree in 'constexpr'ness (this rule only applies to functions).

So this looks like a gcc bug, although it still seems like it could use some clarity in the standard.

Defining constexpr static data members

In

int main() { const int &cs = test::stc; } 

test::stc is odr-used while in

int main () {int array[test::stc];}  

it is not.

The following example from the C++11 Standard supports the above idea.

struct S { static const int x = 0; };
const int &f(const int &r);
int n = b ? (1, S::x) // S​::​x is not odr-used here
: f(S::x); // S​::​x is odr-used here, so a definition is required

Looking at it from practical point of view, cs will be an invalid reference unless test::stc has an address. array, on the other hand, needs just the value of test::stc, which can be evaluated at compile time. array does not need the address of test::stc to be a valid object.

An object that is odr-used must be defined exactly once in a program.

Initialisation of static class member. Why constexpr?

That's because you're initialising it inside the class definition. That's only allowed for constant integral and enumeration types (always) and for constexpr data members (since C++11). Normally, you'd initialise it where you define it (outside the class), like this:

Application.h

class Application {
private:
static Application* app;
}

Application.cpp

Application* Application::app = nullptr;

Note that you need to provide the out-of-class definition even in the constexpr case, but it must not contain an initialiser then. Still, I believe the second case is what you actually want.

Define a static constexpr member of same type of a template class

I think GCC is correct in accepting the given example. This is because the static data member named constant is an ordinary data member variable(although it is still considered a templated entity).

And since constant is an ordinary data member variable, dcl.constexpr#1.sentence-1 is applicable to it:

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.

(emphasis mine)

Thus, as the construct that you have provided after the class template is nothing but an out-of-class definition for the ordinary static data member constant, the given example is well-formed.

template <typename T>
constexpr Test<T> Test<T>::constant {42}; //this is an out-of-class definition for the ordinary static data member variable `constant`


Note

Note that dcl.constexpr#1.sentence-1 doesn't mention templated entity, but instead only mention "variable" and "variable template" and since constant is a variable(ordinary), the same should be applicable to constant.

Can't a class have static constexpr member instances of itself?

Is there a way to achieve the same result?

By "the same result", do you specifically intend the constexpr-ness of
Size::big and Size::small? In that case maybe this would be close enough:

struct Size
{
const unsigned int width = 0;
const unsigned int height = 0;

static constexpr Size big() {
return Size { 480, 240 };
}

static constexpr Size small() {
return Size { 210, 170 };
}

private:

constexpr Size() = default;
constexpr Size(int w, int h )
: width(w),height(h){}
};

static_assert(Size::big().width == 480,"");
static_assert(Size::small().height == 170,"");


Related Topics



Leave a reply



Submit