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-10-23
Priority: 3
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]
[2025-10-23; Reflector poll.]
Set priority to 3 after reflector poll.
Proposed resolution: