This proposal makes it possible to mark destructors as consteval
.
Prior to this proposal, destructors could already be marked constexpr
.
Programs that attempt to invoke a consteval
destructor outside of constant evaluation are ill-formed.
This proposal is largely motivated by P3295 Freestanding constexpr containers and constexpr exception types, which is in turn motivated by P2996 reflection in freestanding environments.
A consteval std::vector
used during reflection computations would be much better if the destructor were consteval
rather than constexpr
.
When the destructor is constexpr
, a default constructed (i.e. empty) constexpr std::vector
's destructor will still execute at runtime, eventually invoking operator delete
.
If the destructor is constevel
, then such a case will be ill-formed, which will properly encourage the developer to refactor the code, likely into a consteval
function.
When this proposal is accepted, P3295 Freestanding constexpr containers and constexpr exception types will be updated to make string
, vector
, and the <stdexcept>
exceptions' destructors freestanding-consteval.
// current struct current_str { char *buf = nullptr; consteval current_str() = default; // allocating constructors omitted for this example constexpr ~current_str() { // "only" constexpr delete[] buf; } }; void test_current() { current_str s; // compiles and emits a delete[] call! } // proposed struct proposed_str { char *buf = nullptr; consteval proposed_str() = default; // allocating constructors omitted for this example consteval ~proposed_str() { // now consteval delete[] buf; } }; void test_future() { proposed_str s0; // ill formed! const proposed_str s1; // fine constexpr proposed_str s2; // fine }
There are teachability benefits to consteval
destructors as well.
If I want to make a class that only exists at compile-time, I may be tempted to mark every function as consteval
.
This runs into the direct problem of destructors.
If the destructor is "only" marked constexpr
, then there's the indirect problem of functions called by destructors.
In many cases, it is not allowable for a constexpr
function to call a consteval
function.
The end result is that users need to jump through hoops to end up with a class that can still end up accidentally leaking into runtime.
struct my_str { char *buf = nullptr; consteval void clear() { delete[] buf; buf = nullptr; } // constexpr can't call consteval here // constexpr ~my_str() { // clear(); // } consteval ~my_str() { // proposed clear(); } };
constexpr
allocations
There is a desire from some in the committee to allow allocating constant evaluated vector
s and string
s persist into runtime.
This is referred to as non-transient constexpr allocations.
This paper doesn't propose such facilities, but the paper is trying to avoid causing issues for those future papers.
With this proposal, a class with consteval
constructors and consteval
destructors can be used at runtime, so long as an allocation doesn't cross the boundary.
I would expect future non-transient constexpr
papers to extend this capability to allocating constructors in the future.
struct boundary_str { char *buf = nullptr; void opaque() const; consteval ~boundary_str() { delete[] buf; } }; void test_opaque() { const boundary_str s; s.opaque(); // executes at runtime! }
This paper was prototyped on clang.
The implementation mainly needed to remove the old diagnostic that prevented consteval
from being placed on destructors, then adding a check in the CodeGen portion of clang to error if a consteval
destructor would be emitted.
In addition, the change was used in the MSSTL (compiled with the prototype clang) to ensure that making vector
's and string
's destructors consteval
in freestanding would produce the desired results and diagnostics.
__cpp_consteval_dtors 20????L
An immediate function is a function, destructor, or constructor that is
- declared with the consteval specifier, or
- an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope.
[Note 11:Default member initializers used to initialize a base or member subobject ([class.base.init]) are considered to be part of the function body ([dcl.fct.def.general]).— end note]
A constexpr or consteval specifier used in the declaration of a function declares that function to be a constexpr function.[Note 3:A function, destructor, or constructor declared with the consteval specifier is an immediate function ([expr.const]).— end note]A destructor, aAn allocation function,or a deallocation function shall not be declared with the consteval specifier.