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 typeexception_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.