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:
- Executing the function at compile-time must now generate storage-backing for static constants in case it gets ODR used -- even if it is never used at runtime (which would be non-zero-overhead), or
- Executing the function at compile-time must now ephemerally create a constant that will be instantiated on each invocation, and finally given storage when a branch calls it with a runtime context (whether or not it's compile-time generated). This would violate existing rules for static storage-duration objects.
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.
Workarounds
There are a few ways you can avoid this limitation depending on what your desired API is, and what the requirements are:
- Throw the constant into file scope, perhaps under a
detail
namespace. This makes your constant global, which may or may not work for your requirements.
- Throw the constant into a
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.
- Make the function a
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 assembly 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
.
foo1
? – Appallfoo1
is here only to show the flaw infoo2
, which I want to be available on both compile and run time. – Meghanmeghannfoo2
. – Meghanmeghannfoo2_caller
is inefficient. – Particulatestatic constexpr
member of a struct/class. If the function this is inside of is meant to be a template, then this could be astatic constexpr
member on a class/struct template – Washkofoo1
isn't necessarily "runtime-only"; if it's called with a constant argument, then clang and gcc are both able to optimizefoo1(4)
into a constant, which I suppose was your goal in creatingfoo2
. Sofoo1
may serve your purpose after all. – Particulatefoo1
still is not allowed to be used in aconstexpr
context though. – Meghanmeghannstd::array<float, foo1(4)>
but you could withfoo2
. – Trimesterstatic
in function does lazy initialization. I think that complicate (too much) implementation ofstatic constexpr
.constexpr
already had some improvements (relaxation on constraints, ...) since C++11 to be more easy to use. – Trimesterconstexpr
function invocations, which are meant to be stateless. It's not impossible to do, it just that current rules complicate this and would require some large changes to allow for it. – Washko