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.

4361. awaitable-receiver::set_value should use Mandates instead of constraints

Section: 33.13.1 [exec.as.awaitable] Status: New Submitter: Lewis Baker Opened: 2025-08-28 Last modified: 2025-09-14

Priority: Not Prioritized

View other active issues in [exec.as.awaitable].

View all other issues in [exec.as.awaitable].

View all issues with New status.

Discussion:

In 33.13.1 [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 33 [exec].

For example: The following set_value member function applies constraints:

While the following set_value member functions do not apply constraints:

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.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 33.13.1 [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 33.9.2 [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
    };
    […]