NVIDIA / stdexec

`std::execution`, the proposed C++ framework for asynchronous and parallel programming.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Interaction with passing lvalue to set_value when sender sends a value

ccotter opened this issue · comments

In the sample code below, compiling with gcc-13 ( g++-13 -std=c++20 set_value.cpp -I stdexec/include -c), my bespoke sender claims it completes with set_value_t(int), although the implementation ends up sending an lvalue v, an int declared on the stack.

#include <stdexec/execution.hpp>
#include <exec/finally.hpp>

struct StdexecSender {

  using is_sender = void;

  template <class Receiver>
  struct Operation {
    Receiver r;

    friend auto tag_invoke(stdexec::start_t, Operation& self) noexcept {
      int v = 0;
      stdexec::set_value((Receiver&&)self.r, v);
    };
  };

  template <class Self, class Receiver>
      requires (std::same_as<StdexecSender, std::remove_cv_t<Self>>)
  friend auto tag_invoke(stdexec::connect_t, Self&& self, Receiver&& r) {
    return Operation<std::decay_t<Receiver>>(std::forward<Receiver>(r));
  }

  using completion_signatures = stdexec::completion_signatures<stdexec::set_value_t(int)>;

};
static_assert(stdexec::sender<StdexecSender>);

void foo() {
    stdexec::sync_wait(exec::finally(stdexec::let_value(StdexecSender{}, [](auto&&...) { return StdexecSender{}; }), stdexec::just()));
}

I get the following diagnostic.

./stdexec/include/exec/../stdexec/execution.hpp:1010:38: warning: 'void stdexec::__debug::_ATTENTION_() [with _Warning = stdexec::_WARNING_<_COMPLETION_SIGNATURES_MISMATCH_, _COMPLETION_SIGNATURE_<stdexec::__receivers::set_value_t(int&)>, _IS_NOT_ONE_OF_<stdexec::__receivers::set_error_t(std::__exception_ptr::exception_ptr), stdexec::__receivers::set_value_t(int)>, _SIGNAL_SENT_BY_SENDER_<stdexec::__let::__sender<stdexec::_Yp<StdexecSender>, main()::<lambda(auto:86&& ...)>, stdexec::__let::let_value_t> > >]' is deprecated: The sender claims to send a particular set of completions, but in actual fact it completes with a result that is not one of the declared completion signatures. [-Wdeprecated-declarations]

The key part is

The sender claims to send a particular set of completions, but in actual fact it completes with a result that is not one of the declared completion signatures.

This makes sense given that technically, I should be sending std::move(v) or something of that nature. That said, the lvalue v is convertible to a plain value (I'm not sure what the type category is called here). My understanding is that set_value is akin to returning from a function. A function declared returning int can write return v - should I be able to write set_value(v) here?

You would agree that there is an important difference between a function that returns and int and one that returns and int&, right? The same issue applies here. A receiver is going to see int&. Is the identity of the parameter important or is it not? What if the sender has both set_value_t(int) and set_value_t(int&) in its list of completion signatures?

This warning is very much intentional. It's to catch problems that can creep in because of these subtle type mismatches that would be very hard to track down otherwise.

Make sense?

EDIT: P.S., You can simply cast the lvalue to a prvalue with set_value(move(rcvr), int(v)).

P.P.S., I am thrilled that this feature is working and helping people find these bugs in their code.

Yes, this makes sense.

P.P.S., I am thrilled that this feature is working and helping people find these bugs in their code.

Yes, I didn't realized stdexec had it! I wrote an incompletely and hacky runtime based (not compile time) version a while back for unifex to help track down similar issues (facebookexperimental/libunifex#545 in particular). Things get interesting in the implementation of algos like let_value which have internal receivers whose set_value take forwarding reference parameters, and care must be taken to send the right values to the original receiver.

I arrived at my example code above while trying to trace through a compiler error with co_await UnifexcSender{} (UnifexSender is a unifex vocabulary sender), which triggered one of the very hard to track down compiler errors that you alluded to. unifex's finally has been a good proxy for catching type mismatches in completions since internally, unifex::finally asserts on the actual type sent against what it expects (via unifex::manual_lifetime_union's assert).

Two related questions on my plate are why sync_wait(StdexecSender{}) does not trigger the diagnostic, and why co_await StdexecSender{} does not either (not sure if related to any type erasure happening).

Is there language in P2300 stating that sending unexpected values is undefined (or other) behavior? I looked a while ago but wasn't able to find it. Not sure if this would be expected to appear in the paper or not.

Ah ok, sync_wait(StdexecSender{}) does not trigger the warning since nothing in that instantiation instantiates completion_signatures_of_t which contains the logic to check the types. exec::finally does instantiate completion_signatures_of_t. Should we add using __checked_signatures [[maybe_unused]] = completion_signatures_of_t(...) into sync_wait and possibly other places?

That sounds like a good idea