From be83f39bba82e4be274532c0bc6b24a9cbfc654d Mon Sep 17 00:00:00 2001 From: Eisenwave Date: Tue, 31 Mar 2026 23:41:33 +0200 Subject: [PATCH] P3373R4 Of Operation States and Their Lifetimes Fixes NB CA-338 (C++26 CD). --- source/exec.tex | 162 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 118 insertions(+), 44 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index 4455a1c991..f80bd95d9c 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -3900,6 +3900,8 @@ \tcode{set_value}, \tcode{set_error}, and \tcode{set_stopped}, respectively. Let the expression \exposid{let-cpo} be one of \tcode{let_value}, \tcode{let_error}, or \tcode{let_stopped}. +Let \exposid{let-tag} denote a unique, empty class type for each of +\tcode{let_value}, \tcode{let_error}, and \tcode{let_stopped}. For a subexpression \tcode{sndr}, let \tcode{\exposid{let-env}(sndr)} be expression-equivalent to the first well-formed expression below: @@ -3931,19 +3933,34 @@ \end{codeblock} except that \tcode{sndr} is evaluated only once. +\pnum +Let \exposid{let-data} denote the following exposition-only class template: +\begin{codeblock} +template +struct @\exposid{let-data}@ { + Sndr @\exposid{sndr}@; // \expos + Fn @\exposid{fn}@; // \expos +}; +\end{codeblock} + +\pnum +Then let the expression \tcode{\exposid{let-cpo}.transform_sender(s, es...)} +be expression-equivalent to: +\begin{codeblock} +@\exposid{make-sender}@(@\exposid{let-tag}@{}, @\exposid{let-data}@{s.template @\exposid{get}@<2>(), s.template @\exposid{get}@<1>()}) +\end{codeblock} +except that \tcode{s} is evaluated only once. + \pnum The exposition-only class template \exposid{impls-for}\iref{exec.snd.expos} -is specialized for \exposid{let-cpo} as follows: +is specialized for \exposid{let-tag} as follows: \indexlibraryglobal{\exposid{impls-for}<\exposid{decayed-typeof}<\exposid{let-cpo}>>} \begin{codeblock} namespace std::execution { - template - void @\exposid{let-bind}@(State& state, Rcvr& rcvr, Args&&... args); // \expos - template<> - struct @\exposid{impls-for}@<@\exposid{decayed-typeof}@<@\exposid{let-cpo}@>> : @\exposid{default-impls}@ { + struct @\exposid{impls-for}@<@\exposid{let-tag}@> : @\exposid{default-impls}@ { static constexpr auto @\exposid{get-state}@ = @\seebelow@; - static constexpr auto @\exposid{complete}@ = @\seebelow@; + static constexpr auto @\exposid{start}@ = @\seebelow@; template static consteval void @\exposid{check-types}@(); @@ -4032,24 +4049,98 @@ \end{itemdescr} \pnum -\tcode{\exposid{impls-for}<\exposid{decayed-typeof}<\exposid{let-cpo}>>::\exposid{get-state}} +Let \exposid{let-state} denote the following exposition-only class template: +\begin{codeblock} +template +struct @\exposid{let-state}@ { + using @\exposid{env_t}@ = decltype(@\exposid{let-env}@(declval())); // \expos + Fn @\exposid{fn}@; // \expos + env_t @\exposid{env}@; // \expos + ArgsVariant @\exposid{args}@; // \expos + OpsVariant @\exposid{ops}@; // \expos + + template + constexpr void @\exposid{impl}@(Rcvr& rcvr, Tag tag, Ts&&... ts) noexcept // \expos + { + using args_t = @\exposid{decayed-tuple}@; + using receiver_type = @\exposid{receiver2}@; + using sender_type = apply_result_t; + if constexpr (is_same_v) { + try { + auto& tuple = @\exposid{args}@.template emplace(std::forward(ts)...); + @\exposid{ops}@.template emplace(); + auto&& sndr = apply(std::move(@\exposid{fn}@), tuple); + using op_t = connect_result_t; + auto mkop2 = [&] { + return connect(std::forward(sndr), + receiver_type{@\exposid{rcvr}@, @\exposid{env}@}); + }; + auto& op = @\exposid{ops}@.template emplace(@\exposid{emplace-from}@{mkop2}); + start(op); + } catch (...) { + constexpr bool nothrow = + is_nothrow_constructible_v && + is_nothrow_applicable_v && + noexcept(connect(declval(), receiver_type{@\exposid{rcvr}@, @\exposid{env}@})); + if constexpr (!nothrow) { + set_error(std::move(@\exposid{rcvr}@), current_exception()); + } + } + } else { + tag(std::move(@\exposid{rcvr}@), std::forward(ts)...); + } + } + + struct @\exposid{receiver}@ { // \expos + @\exposid{let-state}@& state; // \expos + Rcvr& @\exposid{rcvr}@; // \expos + + using receiver_concept = receiver_t; + + template + constexpr void set_value(Args&&... args) noexcept { + @\exposid{state}@.@\exposid{impl}@(@\exposid{rcvr}@, execution::set_value, std::forward(args)...); + } + template + constexpr void set_error(Args&&... args) noexcept { + @\exposid{state}@.@\exposid{impl}@(@\exposid{rcvr}@, execution::set_error, std::forward(args)...); + } + template + constexpr void set_stopped(Args&&... args) noexcept { + @\exposid{state}@.@\exposid{impl}@(@\exposid{rcvr}@, execution::set_stopped, std::forward(args)...); + } + + constexpr env_of_t get_env() const noexcept { + return execution::get_env(@\exposid{rcvr}@); + } + + }; + + using @\exposid{op_t}@ = connect_result_t; // \expos + + constexpr @\exposid{let-state}@(Sndr&& sndr, Fn fn, Rcvr& rcvr) // \expos + : fn(std::move(fn)), env(@\exposid{let-env}@(sndr)), + ops(in_place_type<@\exposid{op_t}@>, std::forward(sndr), + @\exposid{receiver}@{*this, rcvr}) + {} + +}; +\end{codeblock} + +\pnum +\tcode{\exposid{impls-for}<\exposid{let-tag}>::\exposid{get-state}} is initialized with a callable object equivalent to the following: \begin{codeblock} [](Sndr&& sndr, Rcvr& rcvr) requires @\seebelow@ { - auto& [_, fn, child] = sndr; + auto& [_, data] = sndr; + auto& [child, fn] = data; + using child_t = decltype(std::forward_like(child)); using fn_t = decay_t; - using env_t = decltype(@\exposid{let-env}@(child)); using args_variant_t = @\seebelow@; - using ops2_variant_t = @\seebelow@; - - struct @\exposid{state-type}@ { - fn_t @\exposid{fn}@; // \expos - env_t @\exposid{env}@; // \expos - args_variant_t @\exposid{args}@; // \expos - ops2_variant_t @\exposid{ops2}@; // \expos - }; - return @\exposid{state-type}@{@\exposid{allocator-aware-forward}@(std::forward_like(fn), rcvr), - @\exposid{let-env}@(child), {}, {}}; + using ops_variant_t = @\seebelow@; + using state_t = @\exposid{let-state}@<@\exposid{decayed-typeof}@<@\exposid{set-cpo}@>, child_t, fn_t, Rcvr, + args_variant_t, ops_variant_t>; + return state_t(std::forward_like(child), std::forward_like(fn), @\exposid{rcvr}@); } \end{codeblock} @@ -4071,10 +4162,12 @@ let \exposid{as-sndr2} be an alias template such that \tcode{\exposid{as-sndr2}} denotes the type \tcode{\exposid{call-result-t}\&...>}. -Then \tcode{ops2_variant_t} denotes +Then \tcode{ops_variant_t} denotes the type \begin{codeblock} -variant, @\exposid{receiver2}@>...> +variant, + connect_result_t<@\exposid{as-sndr2}@, @\exposid{receiver2}@>...> \end{codeblock} except with duplicate types removed. @@ -4084,31 +4177,12 @@ the types \tcode{args_variant_t} and \tcode{ops2_variant_t} are well-formed. \pnum -The exposition-only function template \exposid{let-bind} -has effects equivalent to: -\begin{codeblock} -using args_t = @\exposid{decayed-tuple}@; -auto mkop2 = [&] { - return connect( - apply(std::move(state.fn), - state.args.template emplace(std::forward(args)...)), - @\exposid{receiver2}@{rcvr, std::move(state.env)}); -}; -start(state.ops2.template emplace(@\exposid{emplace-from}@{mkop2})); -\end{codeblock} - -\pnum -\tcode{\exposid{impls-for}<\exposid{decayed-typeof}<\exposid{let-cpo}>>::\exposid{complete}} +\tcode{\exposid{impls-for}<\exposid{let-tag}>::\exposid{start}} is initialized with a callable object equivalent to the following: \begin{codeblock} -[] - (auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void { - if constexpr (@\libconcept{same_as}@>) { - @\exposid{TRY-EVAL}@(rcvr, @\exposid{let-bind}@(state, rcvr, std::forward(args)...)); - } else { - Tag()(std::move(rcvr), std::forward(args)...); - } - } +[](State& state, Rcvr&) noexcept { + start(get(state.@\exposid{ops}@)); +} \end{codeblock} \pnum