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.
std::execution::let_* Unconditionally Potentially Evaluates std::execution::set_errorSection: 33.9.12.10 [exec.let] Status: New Submitter: Robert Leahy Opened: 2026-01-11 Last modified: 2026-01-11
Priority: Not Prioritized
View other active issues in [exec.let].
View all other issues in [exec.let].
View all issues with New status.
Discussion:
The adoption of P3388 as a resolution to CA-334 allowed asynchronous operations to determine, without a concrete receiver, whether connecting some sub-operation thereof can throw an exception.
The above-described capability is critical when an asynchronous operation connects another asynchronous operation as part of its asynchronous execution,
which is exactly what
std::execution::set_value,
::set_error, and
::set_stopped do.
Enabling the aforementioned algorithms to correctly advertise a
std::execution::set_error_t(std::exception_ptr)
completion signature was the concrete motivation for
P3388
Unfortunately the current library wording for the above-enumerated algorithms prohibits them from taking advantage of P3388.
33.9.2 [exec.snd.expos] paragraph 43.1 describes the process by which the completion signatures of an algorithm are determined:
[...]
CSis the specialization ofcompletion_signaturesthe set of whose template arguments correspond to the set of completion operations that are potentially evaluated as a result of evaluatingop.start().
With this in mind we look to the specification of
std::execution::let_value
et al. in
33.9.12.10 [exec.let].
Connection of the sub-operation is performed via
let-bind
whose signature is (paragraph 5):
template<class State, class Rcvr, class... Args> void let-bind(State& state, Rcvr& rcvr, Args&& args);
Note there is no
noexcept,
conditionally or otherwise.
The effects of
let-bind
are equivalent to (paragraph 12):
using args_t = decayed-tuple<Args...>;
auto mkop2 = [&] {
return connect(
apply(std::move(state.fn),
state.args.template emplace<args_t>(std::forward<Args>(args)...)),
receiver2{rcvr, std::move(state.env)});
};
start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
Note that
mkop2
is not
noexcept,
conditionally or otherwise.
The point of use of
let-bind
is (paragraph 13):
TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward<Args>(args)...));
Where
TRY-EVAL
is described in
33.9.2 [exec.snd.expos]
paragraph 7:
TRY-EVAL(rcvr, expr)is equivalent to:try { expr; } catch(...) { set_error(std::move(rcvr), current_exception()); }if
expris potentially-throwing; otherwise,expr.
The meaning of "potentially-throwing" is drawn from 14.5 [except.spec] paragraph 5:
An expression E is potentially-throwing if
- E is a function call whose postfix-expression has a function type, or a pointer-to-function type, with a potentially-throwing exception specification, or
- [...]
As such even if connecting the sub-operation cannot throw
std::execution::let_value
et al. all potentially evaluate a potentially throwing expression and therefore must advertise
std::execution::set_error_t(std::exception_ptr)
as a possible completion signature.
Note that the wording proposed by P3373 obsoletes this issue.
Proposed resolution:
Update 33.9.12.10 [exec.let] as follows:
[...]
namespace std::execution { template<class State, class Rcvr, class... Args> void let-bind(State& state, Rcvr& rcvr, Args&&... args) noexcept(see below); // exposition only template<> struct impls-for<decayed-typeof<let-cpo>> : default-impls { static constexpr auto get-state = see below; static constexpr auto complete = see below; template<class Sndr, class... Env> static consteval void check-types(); }; }[...]
The exposition-only function template
let-bindhas effects equivalent to:using args_t = decayed-tuple<Args...>; auto mkop2 = [&] noexcept(see below) { return connect( apply(std::move(state.fn), state.args.template emplace<args_t>(std::forward<Args>(args)...)), receiver2{rcvr, std::move(state.env)}); }; start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));Where the exception specification of
mkop2andlet-bindevaluates totrueif each expression contained thereby is not potentially-throwing,falseotherwise.[...]