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.

 

Suggestion:

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.

 

Suggestion:

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.

 

Suggestion:

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. [basic.life] 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.

 

Suggestion:

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