static
variables are not allowed inside a constexpr
function. Which makes sense, since static
would introduce a state to a supposed to be pure function.
However, I don't see why we cannot have a static constexpr
variable in a constexpr
function. It is guaranteed to always have the same value, thus the function would remain pure.
Why would I care? Because static
makes a difference at runtime. Consider this code:
#include <array>
constexpr int at(const std::array<int, 100>& v, int index)
{
return v[index];
}
int foo1(int i) {
static constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
return at(v, i);
}
constexpr int foo2(int i) {
constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
return at(v, i);
}
int foo2_caller(int i) {
return foo2(i);
}
Live: https://gcc.godbolt.org/z/umdXgv
foo1
has 3 asm instructions, since it stores the buffer in the static storage. While foo2
has 15 asm instructions because it is required to allocate and initialize the buffer on each call and the compiler was not able to optimize this away.
Note that foo1
is here only to show the flaw in foo2
. I want to write a function that I'll be able to use at both compile and run time. That's the idea behind foo2
. But we see it cannot be as efficient as the runtime-only foo1
, which is disturbing.
The only meaningful related discussion I found is this, but it does not discuss static constexpr
specifically.
The questions are:
static constexpr
variables might cause?How's this? I stuck the array into a non-type template parameter:
template<std::array<int, 100> v = {5, 7, 0, 0, 5}>
constexpr int foo2(int i) {
return at(v, i);
}
On godbolt, foo2
's disassembly now matches that of your foo1
. This currently works on GCC but not clang; it seems clang is behind the C++20 standard here (see this SO question).
Is my reasoning correct, or do I miss some problem that static constexpr variables might cause?
There are a few edge-cases that static-storage duration would have to consider when dealing with constexpr
variables if they were allowed static-storage duration inside of a constexpr
context.
Objects with static storage duration in a function only get constructed on first entry into the function. It is at this time normally that storage-backing is applied to the constants (for runtime constants). If static constexpr
were allowed in constexpr
context, one of two things has to happen when this is generated at compile-time:
Since constexpr
is inherently stateless throughout the context, applying a static-storage object during the constexpr
function invocation is suddenly adding state between constexpr
invocations -- which is a big change for the current rules of constexpr
. Though constexpr
functions may modify local state, the state is not globally affected.
C++20 also relaxes constexpr
requirements to allow for destructors to be constexpr, which raises more questions such as when the destructor must execute in the above cases.
I'm not saying this isn't a solvable problem; it's just that the existing language facilities make solving this a little complicated without violating certain rules.
With automatic storage duration objects, this is much easier to reason about -- since the storage is coherently created and destroyed at a certain point in time.
Are there any proposals to fix this?
None that I am aware of. There have been discussions on various google groups about rules of it, but I have not seen any proposals for this. If anyone knows of any, please link it in the comments and I will update my answer.
There are a few ways you can avoid this limitation depending on what your desired API is, and what the requirements are:
detail
namespace. This makes your constant global, which may or may not work for your requirements.static
constant in a struct
/class
. This can be used if the data needs to be templated, and allows you to use private
and friend
ship to control access to this constant.static
function on a struct
/class
that contains the data (if this works with your requirements).All three of these approaches work well if the data needs to be a template, although approach 1 will only work with C++14 (C++11 did not have variable templates), whereas 2 and 3 can be used in C++11.
The cleanest solution in terms of encapsulation, in my opinion, would be the third approach of moving both the data and the acting function(s) into a struct
or class
. This keeps the data closely associated to the functionality. For example:
class foo_util
{
public:
static constexpr int foo(int i); // calls at(v, i);
private:
static constexpr std::array<int, 100> v = { ... };
};
Compiler Explorer Link
This will generate identical assemblies to your foo1
approach while still allowing it to be constexpr
.
If throwing the function into a class
or struct
isn't possible for your requirements (perhaps this needs to be a free function?), then you're stuck either moving the data to file-scope (perhaps protected by a detail
namespace convention), or by throwing it into a disjoint struct
or class
that handles the data. The latter approach can use access modifiers and friendship to control the data access. This solution can work, though it admittedly is not as clean:
#include <array>
constexpr int at(const std::array<int, 100>& v, int index)
{
return v[index];
}
constexpr int foo(int i);
namespace detail {
class foo_holder
{
private:
static constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
friend constexpr int ::foo(int i);
};
} // namespace detail
constexpr int foo(int i) {
return at(detail::foo_holder::v, i);
}
Compiler Explorer Link.
This, again, produces identical assembly to foo1
while still allowing it to be constexpr
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With