cplusplus / sender-receiver

Issues list for P2300
Apache License 2.0
20 stars 4 forks source link

remove `get_completion_signatures` #246

Open ericniebler opened 5 months ago

ericniebler commented 5 months ago

it is desirable to compute completion signatures with the type of the receiver instead of with the environment only, as is done in P2300R9. this makes it possible to know whether connect(sndr, rcvr) can throw, which can effect the sender's completion signatures.

the easiest solution is to remove get_completion_signatures and put the completion signatures in the operation state as a nested typedef. doing that involves the following:

  1. change the operation_state concept to require types to have a nested ::completion_signatures alias that denotes a specialization of the completion_signatures<> class template.
    template<class O>
    concept operation_state =
      derived_from<typename O::operation_state_concept, operation_state_t> &&
      valid-completion-signatures<typename O::completion_signatures> &&
      requires (O& o) {
        { start(o) } noexcept;
      };
  2. remove the mandate that connect return an operation state.
  3. add a ::completion_signatures alias to operation-state-task
  4. add a type receiver_archetype as follows:
    struct receiver_archetype {
     using receiver_concept = receiver_t;
     void set_value(auto&&...) noexcept;
     void set_error(auto&&) noexcept;
     void set_stopped() noexcept;
     empty_env get_env() const noexcept;
    };
  5. change completion_signatures_of_t to take a sender type and a receiver type. default the receiver parameter to receiver_archetype. Make similar changes to value_types_of_t, error_types_of_t and sends_stopped.
  6. remove the sender_in concept
  7. change the sender_to concept as follows:
    template<class Sndr, class Rcvr>
    concept sender_to =
      sender<Sndr> &&
      receiver_of<Rcvr, completion_signatures_of_t<Sndr, Rcvr>> &&
      requires (Sndr&& sndr, Rcvr&& rcvr) {
        { connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr)) }
          -> operation_state;
      };   
  8. change sender-of and sender-of-in as follows:

    template<class Sndr, class Rcvr, class... Values>
    concept sender-of-to =
      sender_to<Sndr, Rcvr> &&
      MATCHING-SIG( // see [exec.general]
        set_value_t(Values...),
        value_types_of_t<Sndr, Rcvr, value-signature, type_identity_t>);
    
    template<class Sndr, class... Values>
    concept sender-of = sender-of-in<Sndr, receiver_archetype, Values...>;
  9. change all prose referring to the sender_in concept to instead use sender_to with an appropriate receiver type.
  10. figure out if transform_sender and transform_env need to change.
  11. in the specification of let_value, change let-bind to conditionally handle an exception from connect.
  12. change transform_completion_signatures to propagate type errors.
  13. probably more ...
inbal2l commented 4 months ago

A draft paper is available here: https://docs.google.com/document/d/1J8qWc1aYmNj40R86K0yhFY2OMdH9XlGmJlxsKpkx7Rg/edit?usp=sharing

lewissbaker commented 4 months ago

The other thing that needs to be incorporated here, I believe, is to require receivers to be nothrow-move-constructible.

Without that, it is not possible to infer from a call to connect() with the receiver_archetype being noexcept that the connect() with another receiver is going to be noexcept, because the other receiver might have a potentially-throwing move constructor.

I think that this approach has a lot of benefits and simplifies the implementation.

As it is often the case that an operation-state class has an internal receiver class that it passes to connect() on child operations, and the environment type returned from that receiver's get_env() method is often dependent on the parent operation_state class, it makes sense to

I have two initial concerns, however.

1. The potential impact on compile-times

This approach requires instantiating the entire operation-state class, potentially with a dummy receiver_archetype class, in order to query the completion-signatures.

This can potentially result in a whole raft of instantiations (base-classes, data-members, member-functions, etc.) of operation-states for the whole tree that would not be necessary for just computing the completion-signatures.

I would be interested to see differences in compile-times compared to the alternative approach of just making get_completion_signatures() swap taking an env for taking a receiver.

2. It is now ambiguous whether a sender is non-dependent or not

Simply checking whether a sender can be connected to a receiver_archetype is not sufficient to be able to determine if a sender is dependent or not.

Consider the sender returned by read_env(get_allocator).

This sender can be connected to a receiver_archetype. As its get_env() method returns the empty-env, the read_env sender, which completes with the result of get_allocator(get_env(rcvr)), will complete with std::allocator<void> - the default allocator returned by get_allocator if the env does not provide an allocator.

However, this sender is not a non-dependent sender - the result of this sender does depend on the environment/receiver that is connected to it.

inbal2l commented 4 months ago

@ericniebler @lewissbaker @kirkshoop - it seems like more design discussion is needed here before we can move forward with this change.

ericniebler commented 4 months ago

i wonder how common it is to ask for a sender's completion signatures without also wanting to connect the sender to a receiver.

fwiw, i agree that changing get_completion_signatures to take the receiver is a smaller, simpler, safer fix. someone needs to implement it tho.

lewissbaker commented 4 months ago

i wonder how common it is to ask for a sender's completion signatures without also wanting to connect the sender to a receiver.

The only case I can think of where this would be done is where you want to do early type-checking of senders - which is the whole point of P3164.

fwiw, i agree that changing get_completion_signatures to take the receiver is a smaller, simpler, safer fix. someone needs to implement it tho.

I can potentially implement in squiz. I already have the additional query for is_always_nothrow_connectable and would just need to change the get_completion_signatures() query from taking an optional env to taking an optional receiver.

I will need to work through a potential basic_sender implementation, though, as I do not have this facility in my implementation.