As a disclaimer, I have done my research on this before asking. I found a similar SO question but the answer there feels a bit "strawman" and didn't really answer the question for me personally. I've also referred to my handy cppreference page but that doesn't offer a very "dumbed down" explanation of things most times.
Basically I'm still ramping up on constexpr
, but at the moment my understanding is that it requires expressions to be evaluated at compile time. Since they may only exist at compile time, they won't really have a memory address at runtime. So when I see people using static constexpr
(like in a class, for example) it confuses me… static
would be superfluous here since that is only useful for runtime contexts.
I've seen contradiction in the "constexpr
does not allow anything but compile-time expressions" statement (particularly here at SO). However, an article from Bjarne Stroustrup's page explains in various examples that in fact constexpr
does require the evaluation of the expression at compile time. If not, a compiler error should be generated.
My previous paragraph seems a bit off-topic but it's a baseline necessary to understand why static
can or should be used with constexpr
. That baseline, unfortunately, has a lot of contradicting information floating around.
Can anyone help me pull all of this information together into pure facts with examples and concepts that make sense? Basically along with understanding how constexpr
really behaves, why would you use static
with it? And through what scopes/scenarios does static constexpr
make sense, if they can be used together?
Best Answer
There is one significant difference for function-level static variables, and that has to do with lambda-capture:
You are allowed to odr-use function-local static variables in lambdas without capturing them. That has nothing to do with
constexpr
- however, it usually makes little sense to enforce capture ofconstexpr
variables. Therefore,static
+constexpr
gives a little more comfort in accessing constants from lambdas, consider:In this example, the string view and its contents are constants. The use of a member function however triggers odr-use, which requires us to capture the variable. Alternatively, use
static
+constexpr
.Odr-use means "use according to the One-Definition Rule", and it boils down to "is the address of the object required for that operation". For member functions, the address is required to form the
this
-pointer.Below you'll find the conceptual differences which also explain the effect mentioned above.
constexpr variables are not compile-time values
A value is immutable and does not occupy storage (it has no address), however objects declared as
constexpr
can be mutable and do occupy storage (under the as-if rule).Mutability
Most objects declared as
constexpr
are immutable, but it is possible to define aconstexpr
object that is (partially) mutable as follows:Storage
The compiler can, under the as-if rule, choose to not allocate any storage to store the value of an object declared as
constexpr
. Similarly, it can do such optimizations for non-constexpr variables. However, consider the case where we need to pass the address of the object to a function that is not inlined; for example:The compiler here needs to allocate storage for
precomputed
, in order to pass its address to some non-inlined function. It is possible for the compiler to allocate the storage forprecomputed
andi
contiguously; one could imagine situations where this might affect performance (see below).Standardese
Variables are either objects or references [basic]/6. Let's focus on objects.
A declaration like
constexpr int a = 42;
is gramatically a simple-declaration; it consists of decl-specifier-seq init-declarator-list;
From [dcl.dcl]/9, we can conclude (but not rigorously) that such a declaration declares an object. Specifically, we can (rigorously) conclude that it is an object declaration, but this includes declarations of references. See also the discussion of whether or not we can have variables of type
void
.The
constexpr
in the declaration of an object implies that the object's type isconst
[dcl.constexpr]/9. An object is a region of storage[intro.object]/1. We can infer from [intro.object]/6 and [intro.memory]/1 that every object has an address. Note that we might not be able to directly take this address, e.g. if the object is referred to via a prvalue. (There are even prvalues which are not objects, such as the literal42
.) Two distinct complete objects must have different addresses[intro.object]/6.From this point, we can conclude that an object declared as
constexpr
must have a unique address with respect to any other (complete) object.Furthermore, we can conclude that the declaration
constexpr int a = 42;
declares an object with a unique address.static and constexpr
The IMHO only interesting issue is the "per-function
static
", à laAs far as I know -- but this seems still not entirely clear -- the compiler may compute the initializer of a
constexpr
variable at run-time. But this seems pathological; let's assume it does not do that, i.e. it precomputes the initializer at compile-time.The initialization of a
static constexpr
local variable is done during static initializtion, which must be performed before any dynamic initialization[basic.start.init]/2. Although it is not guaranteed, we can probably assume that this does not impose a run-time/load-time cost. Also, since there are no concurrency problems for constant initialization, I think we can safely assume this does not require a thread-safe run-time check whether or not thestatic
variable has already been initialized. (Looking into the sources of clang and gcc should shed some light on these issues.)For the initialization of non-static local variables, there are cases where the compiler cannot initialize the variable during constant initialization:
Conclusion
As it seems, we can benefit from static storage duration of a
static constexpr
variable in some corner cases. However, we might lose the locality of this local variable, as shown in the section "Storage" of this answer. Until I see a benchmark that shows that this is a real effect, I will assume that this is not relevant.If there are only these two effects of
static
onconstexpr
objects, I would usestatic
per default: We typically do not need the guarantee of unique addresses for ourconstexpr
objects.For mutable
constexpr
objects (class types withmutable
members), there are obviously different semantics between localstatic
and non-staticconstexpr
objects. Similarly, if the value of the address itself is relevant (e.g. for a hash-map lookup).