September 10, 2026·5 min read·engineeringcppconstexprmemorystandards

Persistent constexpr allocation needs to track access paths

P4341 proposes member annotations for persistent constexpr allocations. Interior pointers explain why marking an allocation immutable is not enough.

A vector can expose its elements through a const interface while another pointer still permits modifying the same storage. That alias becomes a language-design problem when the compiler tries to preserve the vector as a constexpr object and place its allocation in immutable storage. Barry Revzin addresses this in P4341R0, dated September 10. The proposal annotates paths through members instead of marking an entire allocation immutable without regard to other pointers into it.

The aim is persistent, also called non-transient, constexpr allocation. Storage created during constant evaluation would survive as part of the resulting program. Types such as vectors and strings could retain allocated contents instead of requiring the allocation to disappear before constant evaluation ends. The proposal remains a design with implementation experience, not a capability that ordinary standard C++ implementations must already provide.

After ten years with C++, I still regard RAII as its strongest argument. The destructor identifies how ownership ends, which turns out to matter even when construction happens during translation. A persistent-allocation design must account for the destructor without letting later runtime mutation invalidate what the compiler concluded during constant evaluation.

Persistence and constant readability are separate decisions

The earlier P1974R1, by Jeff Snyder and Daveed Vandevoorde, separates those properties. An allocation can persist in runtime-mutable storage without its contents becoming usable in constant expressions. Conversely, an allocation whose contents are immutable can support later constant evaluation and placement in read-only storage.

The destructor test asks whether hypothetical constant destruction would release all persistent allocations. It also needs the inputs to that destruction to remain valid. A nested owner demonstrates the problem. If a destructor must read an inner pointer that runtime code can replace, a compile-time destruction result no longer proves what destruction would mean for the later object state.

P4341 retains the requirement that hypothetical destruction frees each surviving allocation. It preserves the end-of-initialization image and discards writes made during that hypothetical destruction. Successfully persisted storage is promoted to static storage, and no runtime destructor runs for it. Independent deallocation is outside the permitted model, because the lifetime is tied to the persisted object.

An allocation-wide mark misses interior pointers

One earlier approach used std::mark_immutable_if_constexpr to identify allocations suitable for persistence. Revzin initially implemented that approach, then found cases where correct marking by an owner was insufficient. An object can contain a vector and a separate mutable pointer into the vector buffer. The vector marks its allocation, but the other pointer still exposes mutable access.

A container element can introduce the same issue internally. A structure holding an integer and a pointer to that integer can retain a mutable path even when the enclosing vector is accessed as const. Marking only the whole allocation cannot distinguish the container access from the pointer path that bypasses its interface.

The proposal therefore checks every pointer or reference into a surviving allocation across the initialized object graph. A member annotation identifies paths the programmer intends to treat as immutable. Other paths remain relevant, so a raw pointer alias cannot disappear from the analysis merely because the owning vector uses an annotation.

The annotation composes through members

The proposed specifier is immutable_if_constexpr, optionally with a boolean condition. A simplified vector marks its storage pointers. A simplified unique owner uses the condition that its element type is const. These are proposed library implementation changes, not annotations users can add to the existing standard library themselves.

Composition matters for a class that provides deep constness while storing its allocation in unique_ptr<int>. The pointer type alone permits mutable access through a const owner. The enclosing class may nevertheless expose only a const reference through its const interface. Annotating the owning member lets that class express its stronger intended rule without changing every underlying pointer type.

This still trusts the class author to mark correctly. It does not prove that every member function respects the claimed const behavior. The proposal narrows the trust requirement to selected member paths, while checking the rest of the graph for contradictory access. That is different from adding a general const-propagation qualifier throughout the type system.

P4341 classifies an allocation as immutable when all paths qualify and at least one uses an annotated member. With no such annotated path, persistence can remain runtime-mutable. A mixture of an annotated path and an unqualified mutable path is ill-formed because the paths express conflicting intent. That last rule rejects the interior-pointer examples that defeated allocation-wide marking.

Nested owners expose the distinction

The paper gives a vector of unique owners as an example. Its vector buffer can be immutable, so its size and its stored owner objects can participate in constant expressions. The integers owned by those objects can remain runtime-mutable. Constant readability of one allocation does not automatically propagate through every pointer stored inside it.

An outer unique owner containing another mutable unique owner has a different problem. Its destructor needs to read the inner owner, so allowing the outer allocation to remain mutable conflicts with the hypothetical destruction test. Making the relevant inner object const can permit persistence while still leaving a further pointee mutable. Each allocation and access path needs its own classification.

The same rules explain why a shared owner is difficult. The paper says its destructor must read the control block, which prevents the required marking even for a const pointee. Reference counting introduces state relevant to destruction that a simpler unique owner does not need. Persistent allocation consequently cannot be granted uniformly to every ownership wrapper.

Allocation identity remains observable

P4341 also proposes retaining allocation identity for constant template parameters. Two vectors with identical contents still produce distinct pointer identities, so template arguments using their data pointers identify different specializations. The scheme ties identity to the originating variable, allocation index and offset, and the paper anticipates an ABI extension.

Revzin reports a Clang implementation that replaces the earlier marking function with the member specifier. Its examples exercise both accepted persistence and rejected mutable aliases. The proposal also shows direct expansion over reflected members without the static-array conversion otherwise needed. That application depends on the persistence model preserving the allocation with the right constant-readability guarantees, including every path by which later code can reach it.