| Document #: | P3303R1 |
| Date: | 2024-06-24 |
| Project: | Programming Language C++ |
| Audience: |
LEWG Library Evolution |
| Reply-to: |
Eric Niebler <eric.niebler@gmail.com> |
“Experience is simply the name we give our mistakes.”
—
Oscar Wilde
“To err is human, but to really foul things up you need a
computer.”
— Paul R. Ehrlich
The paper [P2999R3] “Sender
Algorithm Customization” proposed, among other things, to make sender
algorithms customizable lazily; that is, when their senders are
connected with receivers. LEWG agreed and forwarded P2999 to LWG. Due to
a gross oversight, however, P2999 didn’t propose the wording changes to
connect and
get_completion_signatures that
actually implement the design that LEWG approved. This paper corrects
the oversight by using the new
transform_sender utility in
connect and
get_completion_signatures as
P2999 promised and failed to do.
The changes this paper proposes are:
get_completion_signatures(sndr, env)
first transforms sndr with transform_sender(
and then uses the result in place of
get-domain-late(sndr, env), sndr, env)sndr.
connect(sndr, rcvr) first
transforms sndr with transform_sender(
and then uses the result in place of
get-domain-late(sndr, get_env(rcvr)), sndr, get_env(rcvr))sndr.
R1
connect
and get_completion_signatures to
not cause the evaluation of the
sndr expression when computing
the type of the domain to be passed to
transform_sender.R0
Table
2 in [P2999R3] shows how
that paper proposed to change the
connect customization point. The
table is reproduced below. Note that P2999R3 was targetting a version of
P2300 that still employed
tag_invoke, which has since been
removed.
| Before | After |
|---|---|
|
|
The design shown above is the one that was discussed and voted on in LEWG.
A glance at the proposed wording from P2999R3 shows that no such change was ever made. This most critical part of that paper’s design intent was inadvertantly left out of the wording. Face, meet palm.
This paper proposes no design changes from those described in P2999R3. It “merely” corrects the wording to agree with the design.
[ Editor's note: The changes in this paper are relative to [P2300R9]. ]
[ Editor's note: Change [exec.getcomplsigs] as follows: ]
execution::get_completion_signatures[exec.getcomplsigs]
get_completion_signaturesis a customization point object. Letsndrbe an expression such thatdecltype((sndr))isSndr, and letenvbe an expression such thatdecltype((env))isEnv. Letnew_sndrbe the expressiontransform_sender(decltype(, and letget-domain-late(sndr, env)){}, sndr, env)NewSndrbedecltype((new_sndr)). Thenget_completion_signatures(sndr, env)is expression-equivalent to:
decltype(if that expression is well-formed,new_sndr.get_completion_signatures(env)){}Otherwise,
remove_cvref_t<NewSndr>::completion_signatures{}if that expression is well-formed,Otherwise, if
isis-awaitable<NewSndr,env-promise<Env>>true, then:completion_signatures<SET-VALUE-SIG(await-result-type<NewSndr,env-promise<Env>>),// see [exec.snd.concepts]set_error_t(exception_ptr), set_stopped_t()>{}Otherwise,
get_completion_signatures(sndr, env)is ill-formed.Let
rcvrbe an rvalue receiver of typeRcvr, and letSndrbe the type of a sender such thatsender_in<Sndr, env_of_t<Rcvr>>istrue. LetSigs...be the template arguments of thecompletion_signaturesspecialization named bycompletion_signatures_of_t<Sndr, env_of_t<Rcvr>>. LetCSObe a completion function. If senderSndror its operation state cause the expressionto be potentially evaluated ([basic.def.odr]) then there shall be a signatureCSO(rcvr, args...)SiginSigs...such thatisMATCHING-SIG(decayed-typeof<CSO>(decltype(args)...), Sig)true([exec.general]).
[ Editor's note: Change [exec.connect] paragraphs 2 and 6 as shown below. Paragraphs 3-5 are unchanged but are shown here in their entirety to give context to the surrounding changes. ]
execution::connect[exec.connect]
connectconnects ([async.ops]) a sender with a receiver.The name
connectdenotes a customization point object. For subexpressionssndrandrcvr, letSndrbedecltype((sndr))andRcvrbedecltype((rcvr)), letnew_sndrbe the expressiontransform_sender(decltype(, letget-domain-late(sndr, get_env(rcvr))){}, sndr, get_env(rcvr))NewSndrbedecltype((new_sndr)), and letDSandDRbe the decayed types ofandNewSndrRcvr, respectively.Let
connect-awaitable-promisebe the following class:namespace std::execution { structconnect-awaitable-promise:with-await-transform<connect-awaitable-promise> { DR&rcvr;// exposition onlyconnect-awaitable-promise(DS&, DR& rcvr) noexcept :rcvr(rcvr) {} suspend_always initial_suspend() noexcept { return {}; } [[noreturn]] suspend_always final_suspend() noexcept { terminate(); } [[noreturn]] void unhandled_exception() noexcept { terminate(); } [[noreturn]] void return_void() noexcept { terminate(); } coroutine_handle<> unhandled_stopped() noexcept { set_stopped((DR&&)rcvr); return noop_coroutine(); }operation-state-taskget_return_object() noexcept { returnoperation-state-task{ coroutine_handle<connect-awaitable-promise>::from_promise(*this)}; } env_of_t<const DR&> get_env() const noexcept { return execution::get_env(rcvr); } }; }Let
operation-state-taskbe the following class:namespace std::execution { structoperation-state-task{ using operation_state_concept = operation_state_t; using promise_type =connect-awaitable-promise; coroutine_handle<>coro;// exposition onlyexplicitoperation-state-task(coroutine_handle<> h) noexcept :coro(h) {}operation-state-task(operation-state-task&& o) noexcept :coro(exchange(o.coro, {})) {} ~operation-state-task() { if (coro)coro.destroy(); } void start() & noexcept {coro.resume(); } }; }Let
Vname the type, letawait-result-type<DS,connect-awaitable-promise>Sigsname the type:completion_signatures<SET-VALUE-SIG(V),// see [exec.snd.concepts]set_error_t(exception_ptr), set_stopped_t()>and let
connect-awaitablebe an exposition-only coroutine defined as follows:namespace std::execution { template<class Fun, class... Ts> autosuspend-complete(Fun fun, Ts&&... as) noexcept {// exposition onlyauto fn = [&, fun]() noexcept { fun(std::forward<Ts>(as)...); }; struct awaiter { decltype(fn)fn; static constexpr bool await_ready() noexcept { return false; } void await_suspend(coroutine_handle<>) noexcept {fn(); } [[noreturn]] void await_resume() noexcept { unreachable(); } }; return awaiter{fn}; };operation-state-taskconnect-awaitable(DS sndr, DR rcvr) requires receiver_of<DR, Sigs> { exception_ptr ep; try { if constexpr (same_as<V, void>) { co_await std::move(sndr); co_awaitsuspend-complete(set_value, std::move(rcvr)); } else { co_awaitsuspend-complete(set_value, std::move(rcvr), co_await std::move(sndr)); } } catch(...) { ep = current_exception(); } co_awaitsuspend-complete(set_error, std::move(rcvr), std::move(ep)); } }If
Sndrdoes not satisfysenderor ifRcvrdoes not satisfyreceiver,connect(sndr, rcvr)is ill-formed. Otherwise, the expressionconnect(sndr, rcvr)is expression-equivalent to:
if that expression is well-formed.new_sndr.connect(rcvr)
- Mandates: The type of the expression above satisfies
operation_state.Otherwise,
if that expression is well-formed.connect-awaitable(new_sndr, rcvr)Otherwise,
connect(sndr, rcvr)is ill-formed.