Static constexpr data member initialized off-class, and a possible workaround?
The key point is that constexpr
does not mean "exists only at compile time", but instead "can also be used at compile time. So as long as you don't access the constexpr
variable in a way that requires the value to be accessible at compile-time, it can be treated as if it was a const
.
In fact, with the code you posted, the only place where you ever see the benefits of constexpr
is inside of the implementation file. Anywhere else, it has not choice but to be "degrated" to a regular const
since resolving its value during compilation is impossible.
I think most of your questions can be cleared up by deconstructing the code a bit into an equivalent example:
// MyClass.h
class MyClass {
// static constexpr Data MyData;
static const Data MyData;
};
// MyClass.cpp
namespace {
constexpr Data MyDataValue = ...;
}
const Data MyData::MyData = MyDataValue;
// If you use MyData inside of the implementation file, it gets the conxtepr value.
void MyClass:foo() {
// std::array<float, MyClass::Data.some_member> some_array;
std::array<float, MyDataValue.some_member> some_array; // no problem
}
// Some_other_file.cpp
int foo() {
// That's fine, MyClass::Data will be resolved either at runtime or during lto.
return MyClass::Data.some_member;
}
int bar() {
std::array<float, MyClass::Data.some_member> some_array; // ERROR! Can't be resolved at compile time
}
So what's the point of being able to declare a static constexpr
data member without its immediate initialization?
As long as you define it later inside a header before it's actually used, then everything is fine, which can sometimes come in handy. However, in your case, since you want to define the variable inside an implementation file, you create this two-tiered system where the member is constexpr
in the implementation file, and const
anywhere else.
All this to say: If all your delayed constexpr
definitions are in implementation files like in your posted code, then just make them const
, and use locally a constexpr
in an anonymous namespace inside of the implementation file if you need it there. No need for the macro at all.
What am I allowed to do with a static, constexpr, in-class initialized data member?
Does it mean that a static constexpr variable is not odr-used (and
therefore can be in-class initialized) if it is returned from a
function?
Yes.
Essentially, as long as you treat it as a value, rather than an object, then it is not odr-used. Consider that if you pasted in the value, the code would function identically- this is when it is treated as an rvalue. But there are some scenarios where it would not.
There are only a few scenarios where lvalue-to-rvalue conversion is not performed on primitives, and that's reference binding, &obj
, and probably a couple others, but it's very few. Remember that, if the compiler gives you a const int&
referring to period
, then you must be able to take it's address, and furthermore, this address must be the same for each TU. That means, in C++'s horrendous TU system, that there must be one explicit definition.
If it is not odr-used, the compiler can make a copy in each TU, or substitute the value, or whatever it wants, and you can't observe the difference.
Initializing a static constexpr data member of the base class by using a static constexpr data member of the derived class
For C++14 and 11, Clang is right; however, things have changed in the latest working draft (the future C++17) - see the next section.
The Standard quotes to look for are (from N4140, the draft closest to C++14):
[temp.inst]/1:
[...] The implicit instantiation of a class template specialization
causes the implicit instantiation of the declarations, but not of the
definitions, default arguments, or exception-specifications of the
class member functions, member classes, scoped member enumerations,
static data members and member templates; [...]
[temp.point]/4:
For a class template specialization, [...] the point of instantiation
for such a specialization immediately precedes the namespace scope
declaration or definition that refers to the specialization.
So, the point of instantiation for S<U>
is right before the declaration of U
, with only a forward declaration struct U;
conceptually inserted before, so that the name U
is found.
[class.static.data]/3:
[...] A static data member of literal type can be declared in the
class definition with theconstexpr
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. [...] The member shall still be defined in a
namespace scope if it is odr-used (3.2) in the program and the
namespace scope definition shall not contain an initializer.
According to the paragraph quoted above, the declaration of bar
within the definition of S
, even though it has an initializer, is still just a declaration, not a definition, so it's instantiated when S<U>
is implicitly instantiated, and there's no U::foo
at that time.
A workaround is to make bar
a function; according to the first quote, the function's definition will not be instantiated at the time of the implicit instantiation of S<U>
. As long as you use bar
after the definition of U
has been seen (or from within the bodies of other member functions of S
, since those, in turn, will only be instantiated separately when needed - [14.6.4.1p1]), something like this will work:
template<class T> struct S
{
static constexpr int bar() { return T::foo; }
};
struct U : S<U> { static constexpr int foo = 42; };
int main()
{
constexpr int b = U::bar();
static_assert(b == 42, "oops");
}
Following the adoption of P0386R2 into the working draft (currently N4606), [class.static.data]/3 has been amended; the relevant part now reads:
[...] 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 theconstexpr
specifier, it may be redeclared in
namespace scope with no initializer (this usage is deprecated; see
D.1). [...]
This is complemented by the change to [basic.def]/2.3:
A declaration is a definition unless:
[...]
- it declares a non-inline static data member in a class definition (9.2, 9.2.3),
[...]
So, if it's inline, it's a definition (with or without an initializer). And [dcl.constexpr]/1 says:
[...] A function or static data member declared with the
constexpr
specifier is implicitly an inline function or variable (7.1.6). [...]
Which means the declaration of bar
is now a definition, and according to the quotes in the previous section it's not instantiated for the implicit instantiation of S<U>
; only a declaration of bar
, which doesn't include the initializer, is instantiated at that time.
The changes in this case are nicely summarized in the example in [depr.static_constexpr] in the current working draft:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)
This makes GCC's behaviour standard-conformant in C++1z mode.
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.
Initialize static float constexpr member in the definition (.cpp file) is it possible
With static constexpr
members you cannot leave off the initializer in the class definition. A constexpr
variable must be initialized when declared because it can be used after it is declared in a constant expression. This is detailed in [class.static.data]/3 of the C++11 standard
If a non-volatile 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 ([expr.const]). 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. [ Note: In both these cases, the member may appear in constant expressions. — end note ] The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer.
emphasis mine
So, with that, your code needs to be
// .h file
class Shape {
public:
virtual void getArea();
private:
static constexpr float pi = 3.14; // we initialize here so it can be used.
};
// .cpp file
constexpr float Shape::pi; // we define here so it can be odr-used
Do note that this has changed in C++17. With the introduction of inline variables static constexpr
member variables no longer need to be defined outside of the class. The compiler will handle it for you and ensure only a single defentition of the object exists. You can still define the member if you want, but that ability is deprecated and will most likely be removed in a future standard revision. The new text for [class.static.data]/3 is
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 ([expr.const]). The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) 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 [depr.static.constexpr]). Declarations of other static data members shall not specify a brace-or-equal-initializer.
emphasis mine
and [dcl.constexpr]/1 says that a static constexpr
variable is implicitly inline
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. The consteval specifier shall be applied only to the declaration of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
emphasis mine
Can I initialize a `constexpr static` member outside the class?
constexpr
goes on the initializing declaration of a variable, so just put it outside the class:
struct Header
{
int msgType = -1, len;
static const std::size_t MAX_SIZE;
Header() { len = sizeof(*this); }
};
// ... declaration of subclasses ...
inline constexpr std::size_t Header::MAX_SIZE = std::max({ sizeof(A), sizeof(B), sizeof(C) });
Note that the implicit const
must be spelled out in the declaration. The definition should go in the same header to avoid any translation unit seeing the declaration but not the inline
, which is not allowed.
Initialize static constexpr member variable of class template
You can specialize the class template as
template <int N>
struct Foo {
static constexpr float val { 0.0f };
};
template <>
struct Foo<0> {
static constexpr float val { 3.14f };
};
template <>
struct Foo<1> {
static constexpr float val { 0.1f };
};
Or make a function helper for initialization.
template <int N>
struct Foo {
static constexpr float get_val() {
if constexpr (N == 0) return 3.14f;
else if constexpr (N == 1) return 0.1f;
else return 0.0f;
}
static constexpr float val { get_val() };
};
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.
constexpr initializing static member using static function
The Standard requires (section 9.4.2):
A
static
data member of literal type can be declared in the class definition with theconstexpr
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.
In your "second attempt" and the code in Ilya's answer, the declaration doesn't have a brace-or-equal-initializer.
Your first code is correct. It's unfortunate that gcc 4.6 isn't accepting it, and I don't know anywhere to conveniently try 4.7.x (e.g. ideone.com is still stuck on gcc 4.5).
This isn't possible, because unfortunately the Standard precludes initializing a static constexpr
data member in any context where the class is complete. The special rule for brace-or-equal-initializers in 9.2p2 only applies to non-static data members, but this one is static.
The most likely reason for this is that constexpr
variables have to be available as compile-time constant expressions from inside the bodies of member functions, so the variable initializers are completely defined before the function bodies -- which means the function is still incomplete (undefined) in the context of the initializer, and then this rule kicks in, making the expression not be a constant expression:
an invocation of an undefined
constexpr
function or an undefinedconstexpr
constructor outside the definition of aconstexpr
function or aconstexpr
constructor;
Consider:
class C1
{
constexpr static int foo(int x) { return x + bar; }
constexpr static int bar = foo(sizeof(int));
};
Initializing static constexpr variables and classes inside a struct
A static constexpr
member has a value upon its initialization inside the class { }
scope, but it does not have a location in memory (an address) until it is defined outside the class { }
. The reason is that you may decide to include some or all of its specializations in a link library (e.g. .o
or .so
), or whether to give effectively-inline linkage to specializations by default.
The out-of-class definition is required if the address of the object is ever used, which implies that it must exist as a global variable. On the other hand, if you want the constexpr
member only to exist at compile time, prohibiting global storage allocation, then omitting the definition is a good choice.
By the way, it's not allowed to put the constexpr
specifier on a function that can never be evaluated as a constant expression, such as sayhi
which prints to std::cout
. This is a "no diagnostic required (NDR)" rule, meaning that the compiler might not complain now but the next compiler version might.
Related Topics
Understanding Std::Atomic::Compare_Exchange_Weak() in C++11
How Do Stackless Coroutines Differ from Stackful Coroutines
Why Is My Double or Int Value Is Always 0 After Division
Strange Behavior of Const_Cast
C++, Std::Atomic, What Is Std::Memory_Order and How to Use Them
How to Determine Distance from an Object in a Video
Can't Open Txt Files in C++ Program with Visual Studio 2019
How to Legally Reinterpret_Cast Between Layout-Compatible Standard-Layout Types
Alpha Rendering Difference Between Opengl and Webgl
What am I Allowed to Do with a Static, Constexpr, In-Class Initialized Data Member
Using Visual Studio Project Properties Effectively for Multiple Projects and Configurations
Shared_Ptr and Weak_Ptr Differences
Why Can't I Use a "Break" Statement Inside a Ternary Conditional Statement in C++
Undefined Symbol on a Template Operator Overloading Function
Difference Between Std::Function<> and a Standard Function Pointer