This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
MANDATE-NOTHROW
in CPOs should not enclose CPO argument sub-expressionsSection: 33 [exec] Status: New Submitter: Lewis Baker Opened: 2025-08-25 Last modified: 2025-09-14
Priority: Not Prioritized
View all issues with New status.
Discussion:
There are a number of CPOs defined in 33 [exec] which have behaviour specified in terms of being
expression-equivalent to a MANDATE-NOTHROW
expression.
noexcept
.
However, the way that these CPOs are currently specified in terms of sub-expressions means that we are currently
requiring that all of the expressions passed as arguments to the CPO are also noexcept
. Outside of defining
these CPOs as preprocessor macros, this is unimplementable — and also undesirable behaviour.
For example, 33.7.2 [exec.set.value] defines set_value(rcvr, vs...)
to be equivalent to
MANDATE-NOTHROW(rcvr.set_value(vs...))
for sub-expressions rcvr
and pack of sub-expressions
vs
.
In 33.1 [exec.general] p5 we define MANDATE-NOTHROW(expr)
as expression-equivalent to
expr
but mandate that noexcept(expr)
is true
.
So in the above definition of set_value(rcvr, vs...)
we are actually requiring that the expression
noexcept(rcvr.set_value(vs...))
is true
.
This is only true if all of the sub-expressions are noexcept
, i.e. all of the following expressions are true
.
noexcept(rcvr)
,
(noexcept(vs) && ...)
,
the member-function call to rcvr.set_value(vs...)
including any implicit conversions of arguments.
This means that if, for example, one of the sub-expressions in the pack vs
was a call to some potentially-throwing
function then the overall set_value
expression would be violating the mandates requirement.
struct my_receiver
{
void set_value(int x) noexcept;
};
int get_value() noexcept(false);
my_receiver r;
std::execution::set_value(r, get_value()); // fails MANDATE-NOTHROW mandates
Instead, we need to redefine these CPOs as being expression-equivalent to something that does not require that the
argument expressions to the CPO themselves are noexcept
— only what will be in the body of the CPO function.
set_value(rcvr, vs...)
as expression-equivalent to:
[](auto&& rcvr2, auto&&... vs2) noexcept -> decltype(auto) requires requires { std::forward<decltype(rcvr2)>(rcvr2).set_value(std::forward<decltype(vs2)>(vs2)...); } { return MANDATE-NOTHROW(std::forward<decltype(rcvr2)>(rcvr2).set_value(std::forward<decltype(vs2)>(vs2)...)); }(rcvr, vs...)
The following sections all contain problematic uses of MANDATE-NOTHROW
:
33.5.2 [exec.get.allocator]
33.5.3 [exec.get.stop.token]
33.5.4 [exec.get.env]
33.5.5 [exec.get.domain]
33.5.6 [exec.get.scheduler]
33.5.8 [exec.get.fwd.progress]
33.5.9 [exec.get.compl.sched]
33.5.10 [exec.get.await.adapt]
33.7.2 [exec.set.value]
33.7.3 [exec.set.error]
33.7.4 [exec.set.stopped]
33.8.2 [exec.opstate.start]
Proposed resolution: