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.

4353. Uses of MANDATE-NOTHROW in CPOs should not enclose CPO argument sub-expressions

Section: 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.

The intent of this is that we want to make sure that the call that the CPO dispatches to is marked 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.

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.

For example:

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.

For example, we could change 33.7.2 [exec.set.value] to define 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:

Proposed resolution: