diff --git a/include/execution.hpp b/include/execution.hpp index 91e9b5d1c..f2897e417 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -180,14 +180,14 @@ namespace std::execution { using __single_or_void_t = __back_t; template - using __single_sender_result_t = + using __single_sender_value_t = typename sender_traits> ::template value_types<__single_or_void_t, __single_or_void_t>; template concept __single_typed_sender = typed_sender && - requires { typename __single_sender_result_t; }; + requires { typename __single_sender_value_t; }; ///////////////////////////////////////////////////////////////////////////// // [execution.op_state] @@ -294,6 +294,7 @@ namespace std::execution { // Pass through receiver queries template CPO> + requires (!__one_of) friend auto tag_invoke(CPO cpo, const promise_type& self, As&&... as) noexcept(is_nothrow_invocable_v) -> invoke_result_t { @@ -313,7 +314,7 @@ namespace std::execution { using __nothrow_ = bool_constant>; template - static __impl::__op<__id_t>> __impl(A&& a, R&& r) noexcept { + static __impl::__op<__id_t>> __impl(A&& a, R&& r) { exception_ptr ex; try { // This is a bit mind bending control-flow wise. @@ -342,6 +343,12 @@ namespace std::execution { } public: template <__awaitable A, receiver R> + requires receiver_of> + __impl::__op<__id_t>> operator()(A&& a, R&& r) const { + return __impl((A&&) a, (R&&) r); + } + template <__awaitable A, receiver R> + requires same_as> && receiver_of __impl::__op<__id_t>> operator()(A&& a, R&& r) const { return __impl((A&&) a, (R&&) r); } @@ -474,11 +481,11 @@ namespace std::execution { template struct __awaitable - : __awaitable_base>> { + : __awaitable_base>> { private: using Promise = __t; using Sender = __t; - using Base = __awaitable_base>; + using Base = __awaitable_base>; using __rec = typename Base::__rec; connect_result_t op_; public: diff --git a/std_execution.bs b/std_execution.bs index 9befe48dc..736808cca 100644 --- a/std_execution.bs +++ b/std_execution.bs @@ -2057,7 +2057,15 @@ enum class forward_progress_guarantee { 2. The class template `sender_traits` is used to query a sender type for facts associated with the signal it sends. -3. The primary class template `sender_traits` is defined as if inheriting from an implementation-defined class template sender-traits-base<S> defined as follows: +3. The primary class template `sender_traits` also recognizes awaitables as typed senders. For this clause ([execution]): + + - An awaitable is an expression that would be well-formed as the operand of a `co_await` expression within a coroutine that does not define an `await_transform` member in its promise type. + + - For any type `T`, is-awaitable<T> is `true` if an expression of that type is an awaitable as described above; otherwise, `false`. + + - For an awaitable `a` such that `decltype((a))` is type `A`, await-result-type<A> is an alias for decltype(e), where e is `a`'s await-resume expression ([expr.await]). + +4. The primary class template `sender_traits` is defined as if inheriting from an implementation-defined class template sender-traits-base<S> defined as follows: 1. If has-sender-types<S> is `true`, then sender-traits-base<S> is equivalent to: @@ -2081,7 +2089,39 @@ enum class forward_progress_guarantee { struct sender-traits-base {}; - 3. Otherwise, sender-traits-base<S> is equivalent to + 3. Otherwise, if is-awaitable<S> is `true`, then + + - If await-result-type<S> is cv void then sender-traits-base<S> is equivalent to + +
+            template<class S>
+              struct sender-traits-base {
+                template<template<class...> class Tuple, template<class...> class Variant>
+                  using value_types = Variant<Tuple<>>;
+
+                template<template<class...> class Variant>
+                  using error_types = Variant<exception_ptr>;
+
+                static constexpr bool sends_done = false;
+              };
+            
+ + - Otherwise, sender-traits-base<S> is equivalent to + +
+            template<class S>
+              struct sender-traits-base {
+                template<template<class...> class Tuple, template<class...> class Variant>
+                  using value_types = Variant<Tuple<await-result-type<S>>;
+
+                template<template<class...> class Variant>
+                  using error_types = Variant<exception_ptr>;
+
+                static constexpr bool sends_done = false;
+              };
+            
+ + 4. Otherwise, sender-traits-base<S> is equivalent to
         template<class S>
@@ -2090,16 +2130,16 @@ enum class forward_progress_guarantee {
           };
         
-4. If `sender_traits::value_types` for some sender type `S` is well formed, it shall be a type `Variant>`, where the type packs `Args0` through `ArgsN` are the packs of types the sender `S` passes as - arguments to `execution::set_value` after a receiver object. If such sender `S` invokes `execution::set_value(r, args...)` for some receiver `r`, where `decltype(args)` is not one of the type packs `Args0` through `ArgsN`, the program is ill-formed with no +5. If `sender_traits::value_types` for some sender type `S` is well formed, it shall be a type Variant<Tuple<Args0...>, Tuple<Args1...>, ..., Tuple<ArgsN...>>>, where the type packs Args0 through ArgsN are the packs of types the sender `S` passes as + arguments to `execution::set_value` after a receiver object. If such sender `S` invokes `execution::set_value(r, args...)` for some receiver `r`, where `decltype(args)` is not one of the type packs Args0 through ArgsN, the program is ill-formed with no diagnostic required. -5. If `sender_traits::error_types` for some sender type `S` is well formed, it shall be a type `Variant`, where the types `E0` through `EN` are the types the sender `S` passes as arguments to `execution::set_error` after a receiver - object. If such sender `S` invokes `execution::set_error(r, e)` for some receiver `r`, where `decltype(e)` is not one of the types `E0` through `EN`, the program is ill-formed with no diagnostic required. +6. If `sender_traits::error_types` for some sender type `S` is well formed, it shall be a type Variant<E0, E1, ..., EN>, where the types E0 through EN are the types the sender `S` passes as arguments to `execution::set_error` after a receiver + object. If such sender `S` invokes `execution::set_error(r, e)` for some receiver `r`, where `decltype(e)` is not one of the types E0 through EN, the program is ill-formed with no diagnostic required. -6. If `sender_traits::sends_done` is well formed and `false`, and such sender `S` invokes `execution::set_done(r)` for some receiver `r`, the program is ill-formed with no diagnostic required. +7. If `sender_traits::sends_done` is well formed and `false`, and such sender `S` invokes `execution::set_done(r)` for some receiver `r`, the program is ill-formed with no diagnostic required. -7. Users may specialize `sender_traits` on program-defined types. +8. Users may specialize `sender_traits` on program-defined types. ### `execution::connect` [execution.senders.connect] ### {#spec-execution.senders.connect} @@ -2112,7 +2152,39 @@ enum class forward_progress_guarantee { 1. `tag_invoke(execution::connect, s, r)`, if that expression is valid and its type satisfies `execution::operation_state`. If the function selected by `tag_invoke` does not return an operation state for which `execution::start` starts work described by `s`, the program is ill-formed with no diagnostic required. - 2. Otherwise, `execution::connect(s, r)` is ill-formed. + 2. Otherwise, connect-awaitable(s, r) if is-awaitable<S> is `true` and that expression is valid, where connect-awaitable is a coroutine equivalent to the following: + +
+        operation-state-task connect-awaitable(S&& s, R&& r) requires see-below {
+          exception_ptr e;
+          try {
+            set-value-expr
+          } catch(...) {
+            e = current_exception();
+          }
+          set-error-expr
+        }
+        
+ + where connect-awaitable suspends at the initial suspends point ([dcl.fct.def.coroutine]), and: + + - set-value-expr first evaluates `co_await (S&&) s`, then suspends the coroutine and evaluates `execution::set_value((R&&) r)` if await-result-type<S> is cv void; otherwise, it evaluates `auto&& res = co_await (S&&) s`, then suspends the coroutine and evaluates `execution::set_value((R&&) r, (decltype(res)) res)`. + + If the call to `execution::set_value` exits with an exception, the coroutine is resumed and the exception is immediately propagated in the context of the coroutine. + + [Note: If the call to `execution::set_value` exits normally, then the connect-awaitable coroutine is never resumed. --end note] + + - set-error-expr first suspends the coroutine and then executes `execution::set_error((R&&) r, std::move(e))`. + + [Note: The connect-awaitable coroutine is never resumed after the call to `execution::set_error`. --end note] + + - operation-state-task is a type that models `operation_state`. Its `execution::start` resumes the connect-awaitable coroutine, advancing it past the initial suspend point. + + - Let `p` be an lvalue reference to the promise of the connect-awaitable coroutine, let `b` be an lvalue reference to the receiver `r`, and let `c` be any customization point object excluding those of type `set_value_t`, `set_error_t` and `set_done_t`. Then `std::tag_invoke(c, p, as...)` is expression-equivalent to `std::tag_invoke(c, b, as...)` for any set of arguments `as...`. + + The operand of the requires-clause of connect-awaitable is equivalent to `receiver_of` if await-result-type<S> is cv void; otherwise, it is receiver_of<R, await-result-type<S>>. + + 3. Otherwise, `execution::connect(s, r)` is ill-formed. 3. Standard sender types shall always expose an rvalue-qualified overload of a customization of `execution::connect`. Standard sender types shall only expose an lvalue-qualified overload of a customization of `execution::connect` if they are copyable.