Description
@haggaie makes an interesting observation about the following sender:
let_value(whatever, [](things...) { return just(stuff...); })
let_value
is going to call the lambda with whatever
's value completions, and then immediately connect
and start
the resulting sender (just(stuff...)
). the just
sender's operation state, including copies of stuff...
, will be stored in let_value
's operation state. but after the just
operation is started, stuff...
isn't needed anymore and is just uselessly taking up space in let_value
's operation state.
imagine we add back a submit
customization that connects and immediately starts an operation ... but that returns the operation (instead of returning void
like the old one did). it has an obvious default implementation in terms of connect
and start
:
template <class Sndr, class Rcvr>
auto submit(Sndr&& sndr, Rcvr rcvr)
{
struct storage
{
op(Sndr&& sndr, Rcvr rcvr)
: op_(connect(forward<Sndr>(sndr), move(rcvr)))
{
start(op_);
}
connect_result_t<Sndr, Rcvr> op_;
};
return storage{forward<Sndr>(sndr), move(rcvr)};
}
but a sender like just
could customize it like:
struct _empty {};
template <class... Values>
struct just_sender
{
template <class Self, class Rcvr>
_empty submit(this Self&& self, Rcvr rcvr)
{
std::apply(
[&](auto... vals) noexcept { set_value(move(rcvr), move(vals)...); },
forward<Self>(self).vals_
);
return {};
}
...
tuple<Values...> vals_;
};
in this case, the returned object doesn't even need to store the receiver.
adding this customization point after c++26 would be possible as an extension, but we may not be able to customize we may not be able to change just
for itlet_value
to use it because it would change the ABI of operation states. :-/