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 a constexpr
object that is (partially) mutable as follows:
struct S {
mutable int m;
};
int main() {
constexpr S s{42};
int arr[s.m]; // error: s.m is not a constant expression
s.m = 21; // ok, assigning to a mutable member of a const object
}
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:
struct data {
int i;
double d;
// some more members
};
int my_algorithm(data const*, int);
int main() {
constexpr data precomputed = /*...*/;
int const i = /*run-time value*/;
my_algorithm(&precomputed, i);
}
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 for precomputed
and i
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 is const
[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 literal 42
.)
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
", à la
void foo() {
static constexpr int i = 42;
}
As 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 the static
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:
void non_inlined_function(int const*);
void recurse(int const i) {
constexpr int c = 42;
// a different address is guaranteed for `c` for each recursion step
non_inlined_function(&c);
if(i > 0) recurse(i-1);
}
int main() {
int i;
std::cin >> i;
recurse(i);
}
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
on constexpr
objects,
I would use static
per default:
We typically do not need the guarantee of unique addresses for our constexpr
objects.
For mutable constexpr
objects (class types with mutable
members),
there are obviously different semantics between local static
and non-static constexpr
objects.
Similarly, if the value of the address itself is relevant (e.g. for a hash-map lookup).