constexpr static member before/after C++17
This change is due to the inline variables proposal (P0386). static constexpr
will imply inline
, making definitions redundant.
In Annex D, add a new subclause, “Redeclaration of static constexpr data members”, D.X, with the following content:
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)
};
const int A::n; // redundant declaration (definition in C++2014)
—end example]
Regarding to your questions:
Has this change other reasons than making the additional definitions useless?
In essence, no. Yet it has additional uses besides the one you noted (see this question). This proposal was controversial because it might encourage the use of a mutable global state.
Will it apply on
const static
member or not?
No. Unless you annotate it as inline
.
does it all hold also for the situation at
Class:J
withconstexpr
functions?
Yes. The proposal deals with linking but does not affect initialization rules.
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.
Why can't a static constexpr member variable be passed to a function?
The problem was neither with the code itself, nor with the standard being used. CLion's default compiler does not fully support C++17, so that's why it showed a strange behavior that it could compile static constexpr
member variables, but only as long as they were not passed to functions.
After updating to the most recent compiler version, I was able to run the code successfully without any changes.
Thank you for all your contribution.
Static member access in constant expressions
Clang seems to be in the right. When accessing a static member with the member access syntax [class.static/1]:
A static member s of class X may be referred to using the qualified-id
expression X::s; it is not necessary to use the class member access
syntax to refer to a static member. A static member may be referred to
using the class member access syntax, in which case the object
expression is evaluated.
So s.v()
will cause s
to be evaluated. Now, according to [expr.const/2.11], s
is not a constant expression:
2 An expression e is a core constant expression unless the evaluation
of e, following the rules of the abstract machine, would evaluate one
of the following expressions:[...]
an id-expression that refers to a variable or data member of reference
type unless the reference has a preceding initialization and either:
(2.11.1) - it is initialized with a constant expression or
(2.11.2) - its lifetime began within the evaluation of e;
s
doesn't have a preceding initialization with a constant expression, not in the scope of foo
.
If you want to access the static members based of a function parameter, without hard-coding the type, the way forward is std::remove_reference_t<decltype(s)>
. This is accepted by Clang and GCC both:
#include <type_traits>
struct S
{
constexpr static auto s_v = 42;
constexpr static auto v() { return s_v; }
};
constexpr auto foo(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::v();
return v;
}
constexpr auto bar(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::s_v;
return v;
}
int main() {}
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.
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.
C++ and C++11 class static member, double should use constexpr while int can be const , why?
const
follows the original language specification defined in C++98 and C++03. It was generally disallowed to supply an in-class initalizers for static const
members in C++98. The possibility to do so for static const
objects of integral and enum types in C++98 was part of special treatment given to these types.
constexpr
is a new feature introduced in C++11. It is designed differently and works uniformly for all types.
So, you can just use constexpr
for both integer and floating point types and forget about any non-uniformities.
If you continue to use const
in such contexts, you will have to deal with C++98 legacy. However, C++17 will introduce inline variables, which should also make it possible to use in-class initializers for inline static const
objects of any type.
Related Topics
Does Moving Leave the Object in a Usable State
Changing the Value of Const Variable in C++
C++: Construction and Initialization Order Guarantees
What Are the Distinctions Between the Various Symbols (*,&, etc) Combined with Parameters
C++ Return Value Created Before or After Auto Var Destruction
What Is the Status of N2965 - Std::Bases and Std::Direct_Bases
Constant References with Typedef and Templates in C++
Are Multiple Mutations of the Same Variable Within Initializer Lists Undefined Behavior Pre C++11
Intrinsics for Cpuid Like Informations
How to Avoid Qt App.Exec() Blocking Main Thread
How to Copy a .Txt File to a Char Array in C++
Are Int8_T and Uint8_T Intended to Be Char Types
Identifying Primitive Types in Templates
How Would I Load a Png Image Using Win32/Gdi (No Gdi+ If Possible)