July 25th, 2024

What's so hard about constexpr allocation?

C++20 allows allocations during constant evaluation, but they must be deallocated in the same context, limiting constructs like `constexpr std::vector`. Challenges include constant destruction and access problems, requiring clearer rules.

Read original articleLink Icon
What's so hard about constexpr allocation?

C++20 introduced the ability to perform allocations during constant evaluation, a significant change from previous standards where such allocations were not permitted. However, this capability is limited; any allocation must be deallocated within the same constant evaluation context. This limitation prevents the declaration of `constexpr std::vector` and similar constructs, which remains an issue in C++23 and may persist into C++26. The challenges are twofold: the constant destruction problem, which concerns ensuring that allocations can be safely deallocated, and the constant access problem, which deals with determining when the contents of an allocation can be treated as constant expressions.

The constant destruction problem arises when an allocation persists beyond its intended scope, leading to potential runtime errors if destructors are invoked on memory allocated during compile time. The constant access problem highlights the risk of accessing mutable data through pointers that are declared as constant, which can yield inconsistent results during execution.

To address these issues, the language must establish rules that prevent unsafe allocations while allowing useful constructs like `std::vector`. This involves distinguishing between different types of allocations, such as transient and non-transient allocations, and ensuring that mutable access does not compromise the integrity of constant expressions. The complexity of these problems suggests that a comprehensive solution is still needed to fully leverage `constexpr` allocations in C++.

Link Icon 7 comments
By @jjmarr - 3 months
My current solution to std::vectors not being constexpr is initializing a std::array with an immediately invoked lambda expression. That lets me algorithmically determine the size of the array at compile-time, which is the main use I've wanted out of a constexpr std::vector.
By @mohinder - 3 months
It seems like the C++ language devs are always trying to rein in horses that have already left the barn.
By @its_bbq - 3 months
I don't understand why constexpr allocated memory can't be freed at runtime. It's not so different from statically allocated memory being freed at runtime. Maybe it requires hooks into malloc so the compile time allocations can be known at the start of the run?
By @kstenerud - 3 months
I must be missing something here, because my first thought was "Any constexpr must be const all the way down." Meaning:

- Constexpr objects must be fully const (i.e. they cannot be modified, and non-const methods are not allowed to be called).

- Constexpr objects cannot allow access to mutable data (directly or indirectly). Maybe this is where the devil is in the details? Not sure... It seems that pointers to mutable via constexpr would be a niche thing anyway.

- Constexpr objects always exist for the duration of the program.

- Any allocations made during construction are done via a "const pool" allocator that no-ops deallocation, so that nothing blows up at shutdown. If everything is const/constexpr, the total allocation size should be decidable at compile time, I think?

By @account42 - 3 months
> It is arguably pointless to pursue allowing persistent constexpr allocation if the allocation itself cannot be used as a constant-expression.

It's useful to ensure that some things get calculated at compile time even if all eventuall access is at runtime.

By @miohtama - 3 months
How does Rust solve this problem?
By @38 - 3 months
unpopular opinion, I think constexpr and similar are anti patterns. if you want to memoize values, run a separate script to do that. a computation is not and cannot be constant, this is just some sugar so that people can pretend it is.