Title
awaitable-receiver::set_value should use Mandates instead of constraints
Status
new
Section
[exec.as.awaitable]
Submitter
Lewis Baker

Created on 2025-08-27.00:00:00 last changed 3 days ago

Messages

Date: 2025-09-14.15:43:53

Proposed resolution:

This wording is relative to N5014.

  1. Modify [exec.as.awaitable] as indicated:

    -4- Let `rcvr` be an rvalue expression of type awaitable-receiver, let `crcvr` be a const lvalue that refers to `rcvr`, let `vs` be a pack of subexpressions, and let `err` be an expression of type `Err`. Then:

    1. (4.1) — If constructible_from<result-type, decltype((vs))...> is satisfied, tThe expression `set_value(rcvr, vs...)` is equivalent to:

      try {
        rcvr.result-ptr->template emplace<1>(vs...);
      } catch(...) {
        rcvr.result-ptr->template emplace<2>(current_exception());
      }
      rcvr.continuation.resume();
      

      Otherwise, `set_value(rcvr, vs...)` is ill-formedMandates: constructible_from<result-type, decltype((vs))...> is satisfied.

    2. (4.2) — […]

    3. (4.3) — […]

    4. (4.4) — […]

  2. Modify [exec.snd.expos] after p25 as indicated:

    […]
    template<class Sndr, class Rcvr, class Index>
      requires valid-specialization<env-type, Index, Sndr, Rcvr>
    struct basic-receiver { // exposition only
      using receiver_concept = receiver_t;
      
      using tag-t = tag_of_t<Sndr>; // exposition only
      using state-t = state-type<Sndr, Rcvr>; // exposition only
      static constexpr const auto& complete = impls-for<tag-t>::complete; // exposition only
      
      template<class... Args>
        requires callable<decltype(complete), Index, state-t&, Rcvr&, set_value_t, Args...>
      void set_value(Args&&... args) && noexcept {
        complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<Args>(args)...);
      }
      
      template<class Error>
        requires callable<decltype(complete), Index, state-t&, Rcvr&, set_error_t, Error>
      void set_error(Error&& err) && noexcept {
        complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err));
      }
      
      void set_stopped() && noexcept
        requires callable<decltype(complete), Index, state-t&, Rcvr&, set_stopped_t> {
        complete(Index(), op->state, op->rcvr, set_stopped_t());
      }
      
      auto get_env() const noexcept -> env-type<Index, Sndr, Rcvr> {
        return impls-for<tag-t>::get-env(Index(), op->state, op->rcvr);
      }
      
      basic-state<Sndr, Rcvr>* op; // exposition only
    };
    […]
    
Date: 2025-09-14.15:43:53

In [exec.as.awaitable] bullet 4.1 the awaitable-receiver::set_value member function is defined as having a constraint that the result-type is constructible from the values.

If constructible_from<result-type, decltype((vs))...> is satisfied, the expression `set_value(rcvr, vs...)` is equivalent to:

try {
  rcvr.result-ptr->template emplace<1>(vs...);
} catch(...) {
  rcvr.result-ptr->template emplace<2>(current_exception());
}
rcvr.continuation.resume();

Otherwise, `set_value(rcvr, vs...)` is ill-formed.

Should we be using mandates here instead of constraints (or alternatively just drop the constraint altogether)? There shouldn't be any need to change behaviour based on whether or not the receiver's completion methods are well-formed or not.

It is worth noting that there is inconsistent use of constraints on `set_value` methods in other receiver implementations throughout [exec].

For example: The following `set_value` member function applies constraints:

  • In [exec.snd.expos] basic-receiver::set_value constrains that check that it can accept those specific value arguments

While the following `set_value` member functions do not apply constraints:

  • In [exec.let] receiver2::set_value

  • In [exec.spawn.future] spawn-future-receiver::set_value

  • in [exec.sync.wait] sync-wait-receiver::set_value

We should probably try to be consistent on whether or not `set_value` implementations should use constraints or mandates. Given that it is not allowed to form calls to the receiver unless that overload is present in the `completion_signatures`, it may be worth just making them all mandates. This would tend to make uses of the `receiver_of` concept less useful as satisfying receiver_of<R, Sig> would not necessarily guarantee that actually trying to call each of `R`'s corresponding completion functions will result in a well-formed program. It is arguable that this is already the status-quo, however.

History
Date User Action Args
2025-09-14 15:43:53adminsetmessages: + msg15040
2025-09-14 15:43:53adminsetmessages: + msg15039
2025-09-14 15:43:53adminrestored
2025-09-14 14:13:50adminretired
2025-09-14 14:13:50adminsetmessages: - msg15037, msg15038
2025-09-14 13:36:33adminsetmessages: + msg15038
2025-08-27 00:00:00admincreate