1. Revision history
1.1. Changes since R1
-
Add note on [P3068R0] feedback at Tokyo 2024.
-
Discuss the use of two feature-testing macros.
-
Discuss why
is not being markedstd :: rethrow_exception
.constexpr -
Rename the "Overview" section to "Introduction" in line with WG21 conventions.
1.2. Changes since R0
-
Mention [P3068R0].
-
Bring up future-proofing in § 3 Motivation.
-
Add missing synopsis edits to § 6 Proposed wording.
-
Minor editorial fixes/improvements.
2. Introduction
I propose to mark the function
.
Currently, this can be done because there can never be an active exception during constant evaluation.
would always return zero during constant evaluation.
Similarly,
would always return a null pointer.
[P2996R1] recommends exceptions as an error handling mechanism for reflections,
during constant evaluation. [P3068R0] proposes allowing exception throwing in constant expressions.
If exceptions were throwable in constant expressions, marking these functions
would simply be part of exception support. However, these proposals is not a prerequisite to this proposal.
Note: [P3068R0] has been seen by EWG at Tokyo 2024 but was not forwarded to CWG, largely due to the lack of implementation experience.
3. Motivation
The motivation is the same as allowing
-
blocks in
functions,
a feature added to C++20 thanks to [P1002R1].
Allowing the use of
in constant expressions makes
metaprogramming easier and eliminates special
cases.
A common use case for
is in the implementation of RAII types which
execute a function upon destruction, but only if an exception was (not) thrown in the current
scope.
This is utilized by
and
; see [N4806].
constexpr
stack, one may write:
constexpr value_type pop () { // Only decrease the size of the stack if no exception was thrown during // copy/move construction of the returned object. // This ensures a strong exception guarantee. std :: scope_success _ {[ this ] { m_size -- ; }}; return std :: move ( top ()); }
It is reasonable to mark such code
, and ideally
would not be an
obstacle to this.
Besides the quality-of-life aspect, we want to future-proof code.
If the user circumvents
by guarding its use with an
block, this makes the assumption that exceptions aren’t throwable in
constant expression.
That may be true now, but could change in the future, in which case the user will have to rewrite
their code to avoid this assumption.
Furthermore, it makes sense to mark
' sister function,
.
This is done purely for the purpose of consistency.
I am not aware of any concrete example of
’s
lack of
being an obstacle.
4. Possible implementation
4.1. constexpr uncaught_exceptions
constexpr int uncaught_exceptions () noexcept { if consteval { return 0 ; } else { return __uncaught_exceptions_impl (); } }
It is obviously possible for the user to wrap
like this themselves (e.g. [ScopeLite]), but this is an unnecessary burden.
4.2. constexpr current_exception
constexpr exception_ptr current_exception () noexcept { if consteval { return exception_ptr ( nullptr ); } else { return __current_exception_impl (); } }
4.3. constexpr
exception_ptr
would also need to be made a literal type.
All major three standard libraries implement
as a wrapper class for
,
which makes this easily possible.
-
See MSVC STL, exception.
Simply mark all special member functions
and if necessary, guard their implementation
with an
block.
It is impossible to create an
that is not a null pointer during constant
evaluations.
4.4. Non-trivial implementations
[P2996R1] suggests allowing
in constant expressions.
This would mean that
,
,
and
would no longer have such trivial implementations,
and further functions such as
may be marked
.
The bare minimum compiler support needed for this is:
-
The compiler must track all active exceptions "magically", so that
returns the correct amount, andstd :: active_exceptions ()
returns the current exception. This needs compiler support because such mutable global state normally doesn’t exist in constant expressions.std :: current_exception () -
behaves like a type-erased, reference-counted smart pointer. [P2738R1] has been accepted into C++26, addingstd :: exception_ptr
cast fromconstexpr
. This makes the implementation of such type-erasure invoid *
feasible.constexpr std :: exception_ptr
4.5. Impact on ABI
Multiple functions, including member functions of
would become
inline functions if marked
.
To remain ABI-compatible with existing software, it is necessary to emit these inline function
into the runtime library.
libstdc++ already conditionally does this by marking member functions of
.
Therefore:
-
libstdc++ is not affected.
-
MSVC STL already defines all member function of
as inline functions, and is also not affected.exception_ptr -
libc++ would need to apply similar compatibility measures as libstdc++.
5. Design considerations
While the proposal is largely an "add
proposal",
there are still a few debateable aspects, discussed below.
5.1. Feature-testing
The proposal currently proposes feature-testing through:
-
A new
for__cpp_lib_constexpr_current_exception
.std :: current_exception -
An update to the existing
macro.__cpp_lib_uncaught_exceptions
This is intuitive because the
macro wouldn’t be used for feature-detection
of
.
However, two ways of feature-testing for such a small proposal may be seen as excessive.
Note: This design choice is currently awaiting feedback from SG10.
5.2. std :: rethrow_exception
The proposal does not mark
.
Without the ability to actually have exceptions during constant evaluation
(as proposed by [P3086R0]),
would be an "always UB" function.
Based on the function’s preconditions, it is not possible to rethrow a null
.
Therefore,
is of little use during constant evaluation.
Existing (common) code of the form
if ( p ) std :: rethrow_exception ( p );
... can already be constant-evaluated.
Marking it
could be more easily justified if the proposal also altered the existing
semantics to do nothing when rethrowing a null pointer;
however, that is arguably undesirable.
After all, such a change would encourage the pattern of omitting the
part from the code above,
which may harm readability since it suggests that rethrowing always happens, even if it only
conditionally happens.
Such a change would also be asymmetrical with
,
which results in
being called if there is no current exception, not in a no-op.
6. Proposed wording
The proposed changes are relative to the working draft of the standard as of [N4917].
Update subclause 17.3.2 [version.syn], paragraph 2 as follows:
#define __cpp_lib_constexpr_current_exception 202401L // freestanding, also in <exception> [...] #define __cpp_lib_uncaught_exceptions 201411L 202401L // freestanding, also in <exception>
Update subclause 17.9.2 [exception.syn] as follows:
constexpr int uncaught_exceptions () noexcept ;
using exception_ptr = unspecified ; constexpr exception_ptr current_exception () noexcept ;
Update subclause 17.9.6 [uncaught.exceptions] as follows:
constexpr int uncaught_exceptions () noexcept ;
Update subclause 17.9.7 [propagation], paragraph 2 as follows:
is a literal type([basic.types.general]) which meets the requirements of Cpp17NullablePointer (Table 36). All expressions which must be valid for a Cpp17NullablePointer are constant expressions for a null value of type
exception_ptr .
exception_ptr
Note: This wording is slightly work-in-progress.
Update subclause 17.9.7 [propagation],
as follows:
constexpr exception_ptr current_exception () noexcept ;
7. Acknowledgements
The original idea for this paper and a portion of its content have been adopted from a proposal draft by Morwenn.