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 friendship 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?foo1is here only to show the flaw infoo2, which I want to be available on both compile and run time.foo2.foo1still is not allowed to be used in aconstexprcontext though.