When and Why Would You Use Static with Constexpr

Does static constexpr variable inside a function make sense?

The short answer is that not only is static useful, it is pretty well always going to be desired.

First, note that static and constexpr are completely independent of each other. static defines the object's lifetime during execution; constexpr specifies that the object should be available during compilation. Compilation and execution are disjoint and discontiguous, both in time and space. So once the program is compiled, constexpr is no longer relevant.

Every variable declared constexpr is implicitly const but const and static are almost orthogonal (except for the interaction with static const integers.)

The C++ object model (§1.9) requires that all objects other than bit-fields occupy at least one byte of memory and have addresses; furthermore all such objects observable in a program at a given moment must have distinct addresses (paragraph 6). This does not quite require the compiler to create a new array on the stack for every invocation of a function with a local non-static const array, because the compiler could take refuge in the as-if principle provided it can prove that no other such object can be observed.

That's not going to be easy to prove, unfortunately, unless the function is trivial (for example, it does not call any other function whose body is not visible within the translation unit) because arrays, more or less by definition, are addresses. So in most cases, the non-static const(expr) array will have to be recreated on the stack at every invocation, which defeats the point of being able to compute it at compile time.

On the other hand, a local static const object is shared by all observers, and furthermore may be initialized even if the function it is defined in is never called. So none of the above applies, and a compiler is free not only to generate only a single instance of it; it is free to generate a single instance of it in read-only storage.

So you should definitely use static constexpr in your example.

However, there is one case where you wouldn't want to use static constexpr. Unless a constexpr declared object is either ODR-used or declared static, the compiler is free to not include it at all. That's pretty useful, because it allows the use of compile-time temporary constexpr arrays without polluting the compiled program with unnecessary bytes. In that case, you would clearly not want to use static, since static is likely to force the object to exist at runtime.

When and why would you use static with constexpr?

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).

Why constexpr should be static?

static has a number of meanings. In classes (per your comment), it means that the member is associated with the class, and not a specific instance (object) of that class.

For a constexpr, that makes a lot of sense. That's typically initialized by a value known to the compiler, and not from ctor arguments.

Difference between constexpr and static constexpr global variable

In your current example there is no difference: On variable declarations, constexpr implies const, and a const variable at namespace scope has internal linkage by default (so adding static does not change anything).

In C++14, you cannot declare a variable as constexpr and have it have external linkage unless you only ever do this in one single translation unit. The reason is that constexpr variables require an initializer, and a declaration with initializer is a definition, and you must only have a single definition.

However, what you can do is use a normal integral constant, which you can declare (not define) as extern, and in the translation unit where it is defined it can even be used as a constant expression:

lib.h:

extern const int a;

lib.cpp:

#include "lib.h"

const int a = 10;

int b[a] = {1, 2, 3}; // OK in this translation unit

In C++17, there is a new feature "inline variables" which lets you say:

inline constexpr int a = 10;

And this is an "inline definition" that can appear repeatedly, and each definition defines the same entity (just like all the other "inline" entities in the language).

constexpr vs. static const: Which one to prefer?

As long as we are talking about declaring compile-time constants of scalar integer or enum types, there's absolutely no difference between using const (static const in class scope) or constexpr.

Note that compilers are required to support static const int objects (declared with constant initializers) in constant expressions, meaning that they have no choice but to treat such objects as compile-time constants. Additionally, as long as such objects remain odr-unused, they require no definition, which further demonstrates that they won't be used as run-time values.

Also, rules of constant initialization prevent local static const int objects from being initialized dynamically, meaning that there's no performance penalty for declaring such objects locally. Moreover, immunity of integral static objects to ordering problems of static initialization is a very important feature of the language.

constexpr is an extension and generalization of the concept that was originally implemented in C++ through const with a constant initializer. For integer types constexpr does not offer anything extra over what const already did. constexpr simply performs an early check of the "constness" of initializer. However, one might say that constexpr is a feature designed specifically for that purpose so it fits better stylistically.

static constexpr vs constexpr in function body?

The main difference between those two declarations is the lifetime of the objects. When writing the question, I thought that using constexpr instead of const would place that object into the .rodata section. But, I was wrong. The constexpr keyword, here, only provides that the object can be used at compile-time functions. So, the object is actually created in the stack during run-time and destroyed when leaving the function's body.
On the other hand, the static constexpr object is an object placed in the .rodata section. It is created at the first time we call the wrapping function. In addition, thanks to the constexpr, it is also available during compile-time.

Are constexpr functions implicitly static?

constexpr functions are implicitly inline.

inline is a linking feature. An inline function with definitions in different compilation units is not an error; if their definitions vary, your program is ill-formed no diagnostic required, but if they have the same definition then all but one version is discarded and that version is used.

static, on a non-method function, is also a linking feature. A static definition is not shared outside of its compilation unit; the compilation unit does not 'advertise' that it has a definition for isThree.

static on a method function has nothing to do with linking. In that case, it just means that this is not implicitly passed to the function. A method with/without this it doesn't work has differences, but they are mostly unrelated to them being constexpr. Note that in at least c++14 a constexpr method that doesn't use this can still be constant evaluated. Some versions of c++ make constexpr methods implicitly const; c++17 does not.

&isThree in one compilation unit and &isThree in another can (and usually do) vary when static (barring aggressive ICF, which is a matter for a different question). When inline they may not vary.

inline functions are shared between compilation units. Their full definition is also often visible in all compilation units aware of it, so it makes compiler "inlining" (as opposed to the keyword) your code easier. static are not. constexpr functions are implicitly inline, but not implicitly static.

Note that constexpr functions can be evaluated in a runtime context sometimes. When evaluated in a compile time context, their inline vs static or linkage state really doesn't matter.

constexpr means other things as well, but you wanted to know the difference between two different constexpr declarations, and none of those meanings change.

What's the difference between static constexpr and static inline variables in C++17?

You don't have to specify an initializer for mySecondVar at the point of declaration. Nor is the initializer required to be constexpr itself.

This means that if we attempt to define myFirstVar like this:

class MyClass {
static constexpr int myFirstVar;
};

int MyClass::myFirstVar = 1;

Or like this:

#include <cstdlib>

class MyClass {
static constexpr int myFirstVar = rand();
};

It's ill-formed either way. constexpr semantics demand it and for a good reason.

The inline specifier approach allows us to include a static variable definition in the header itself, without the initializer being constexpr; or if the initializer is fairly complex it doesn't have to be in the class definition itself.

So this is a perfectly valid header in C++17:

#include <cstdlib>

class MyClass {
static const int mySecondVar;
};

inline const int MyClass::mySecondVar = rand();

The standard promises us that all translation units that include the header will see the same value for the variable, even though we won't know what it is until run-time.

It's mostly a library writers tool. Assume your library is header only. Then in the olden days, what were your options if you needed a static constant defined like this?

Well, you could have an object file shipped with your library. It will be compiled from a translation unit that contains just the constant definition. Now the library isn't header-only.

Or you could rely on inline functions instead. The inline variable effect can be achieved with the following:

class MyClass {
static inline int mySecondVar();
};

inline int MyClass::mySecondVar() {
static const int value = rand();
return value;
}

But it's hidden behind a wall of syntax, and masks what is essentially a constant, with a function call operator.



Related Topics



Leave a reply



Submit