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.

4501. std::execution::let_* Unconditionally Potentially Evaluates std::execution::set_error

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

[...] CS is the specialization of completion_signatures the set of whose template arguments correspond to the set of completion operations that are potentially evaluated as a result of evaluating op.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 expr is 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

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-bind has 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 mkop2 and let-bind evaluates to true if each expression contained thereby is not potentially-throwing, false otherwise.

[...]