There are some issues in constexpr dynamic allocation in C++20 IMO.


Part 1. Destructor calls in constant evaluation


A plain (pseudo-)destructor call (note that a pseudo-destructor is made destroying the object via P0593R6) is not restricted in constant evaluation, while a std::(ranges::)destroy_at is restricted. Because destroying an object is not needed to modify any scalar (sub)object, so the current specification in [expr.const] apparently allow evaluation of a constant expression to destroy an object that whose lifetime didnít begin within it, e.g. a global constexpr object.



Such restriction should exist for plain (pseudo-)destructor calls, not for std::(ranges::)destroy_at only. This part should be addressed as a CWG issue.



Part 2. Program-defined specializations for std::allocator


Since C++20, std::allocator<T>::allocate/deallocate can perform constexpr dynamic allocation/deallocation, but it is unspecified how are such operations done. On the other hand, users are still allowed to provide program-defined specializations for std::allocator, while the allocate/deallocator member functions of these specializations must be manually implemented. As a result, thereís no portable way these specializations constexpr dynamic allocation/deallocation, or even no way if the compiler uses some non-extensible magic, so itís doubtful that whether these specializations can achieve the requirement in [namespace.std]/2.



Move the magic into free function templates (such as std::allocate_n/deallocate_n), and make std::allocator<T>::allocate/deallocate call them.



Part 3. Direct list-initialization in constant evaluation


std::(ranges::)construct_at effectively allows us to perform direct (non-list) initialization on a given location in constant evaluation, but default initialization and direct list-initialization are not allowed yet. Although the utility performing default initialization, default_construct_at, is proposed in P2283, there is no general way simulate a placement-new that performs direct list-initialization by function  templates.


[expr.const] currently doesnít directly allow a plain non-allocating placement-new expression, which looks an oversight.



Introduce the item "type-corresponding placement new-expression". A placement new-expression is type-corresponding if

-      it would select a non-allocating allocation function, and

-      its type-id or new-type-id doesnít denotes an array of non-constant length, and

-      the expression in its new-placement, after removing any explicit cast operator to cv void*, is a such expression that becomes a T* prvalue before converted to cv void*, where T is same as the type denoted by type-id/new-type-id ignoring top-level cv-qualification.

The "corresponding pointer value" of a type-corresponding placement-new expression is the aforementioned T* prvalue.

Allow a type-corresponding placement new-expression whose corresponding pointer value satisfies the current requirements for std::(ranges::)construct_at in constant evaluation.


As a result, we can make std::(ranges::)construct_at non-magic, and implement default_construct_at directly.



Part 4. Storage reuse in constant evaluation


Currently storage reuse is permitted in constant evaluation, however, it may be not clear whether skipping a non-trivial destructor call result in UB. [] says ďand any program that depends on the side effects produced by the destructor has undefined behaviorĒ, and I donít know whether the requirement is diagnosable in constant evaluation.



Unclear. Maybe we can require that in constant evaluation, a skipped destructor call shall must be no-op, like constant destruction? This part should be addressed as a CWG issue. Related to EDIT 2342.



Other questions and concerns


Should we clarify that no allocation/deallocation function is called by new-/delete-expressions in constant evaluation? Note that these functions are not constexpr as for now.


Can  we remove non-allocating allocation functions (along with their corresponding deallocation functions) and make their signatures built-in candidates? IMO it is cleaner that ::new ((void*)buf) int{} no longer requires <new> and no longer call any function semantically.



Jiang An