Does Static Constexpr Variable Inside a Function Make Sense

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.

Should we define variable with constexpr in a function

There is an important difference.

A static local variable is initialized on the first call, so that in subsequent calls its declaration is no longer used. In particular, the constructor of the static local variable is called only once.
A static const is a static variable which is also a constant: thus, its value, once initialized on the first call, cannot be changed anymore.

A constexpr expression is an expression that can be evaluated at compile time. Because of that, a constexpr variable can, for instance, be used as nontype template parameter.

Generically, you would not be able to use a simple static const as template parameter, because even if it is a const, its actual value might not be determined at compile time; for example:

void f(int x)
{
static const i = x;
std::array<int, i> a; // Compile error!
}

Though, variables of integral type, that are initialized as constant expression can still be used as nontype template parameters. So, in the code of the question, the variable i of func2 could be used as nontype template parameter.

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

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

static constexpr variable vs function

constexpr functions

Functions have an advantage that free variables do not have (until C++14 that is): they can easily be templated without some class boilerplate. That means you can have your pi with a precision depending on a template argument:

template<typename T>
constexpr T pi();

template<>
constexpr float pi() { return 3.14f; }

template<>
constexpr double pi() { return 3.1415; }

int main()
{
constexpr float a = pi<float>();
constexpr double b = pi<double>();
}

However, if you decide to use a static member function instead of a free function, it won't be shorter nor easier to write than a static member variable.

constexpr variables

The main advantage of using a variable is that... well. You want a constant, right? It clarifies the intent and that may be one of the most important points here.

You could still have an equivalent behaviour with a class, but then, you would have to use it like this if your class is a class containing miscellaneous mathematics constants:

constexpr float a = constants<float>::pi;

Or like this if your class is only meant to represent pi:

constexpr double = pi<double>::value;

In the first case, you may prefer to use variables since it will be shorter to write and that will really show that you are using a constant and not trying to compute something. If you just have a class representing pi, you could however go with a free constexpr function instead of a whole class. It would IMHO be simpler.

C++14: constexpr variable templates

However, note that if you choose to use C++14 instead of C++11, you will be able to write the following kind of constexpr variable templates:

template<typename T>
constexpr T pi = T(3.1415);

That will allow you to write your code like this:

constexpr float a = pi<float>;

Starting from C++14, this may be the preferred way to do things. If you are using an older version of the standard, the first two paragraphs still hold.



Related Topics



Leave a reply



Submit