2121namespace exec {
2222 using namespace stdexec ;
2323
24- template <typename T>
24+ template <class T >
2525 concept __contextually_convertible_to_bool =
2626 requires (const T c) {
2727 { (static_cast <const T&&>(c) ? false : false ) } -> same_as<bool >;
2828 };
2929
30- template <typename T>
30+ template <class T >
3131 static constexpr bool __nothrow_contextually_convertible_to_bool_v =
3232 noexcept ((std::declval<const T&&>() ? (void )0 : (void )0 ));
3333
34- template <typename T>
34+ template <class T >
3535 concept __nothrow_contextually_convertible_to_bool =
3636 __contextually_convertible_to_bool<T> &&
3737 __nothrow_contextually_convertible_to_bool_v<T>;
@@ -165,16 +165,25 @@ namespace exec {
165165
166166
167167 struct __null_tail_receiver {
168- void set_value () noexcept {}
169- void set_error (std::exception_ptr) noexcept {}
170- void set_done () noexcept {}
168+ friend void tag_invoke (set_value_t , __null_tail_receiver&&, auto &&...) noexcept {}
169+ friend void tag_invoke (set_stopped_t , __null_tail_receiver&&) noexcept {}
170+ friend __empty_env tag_invoke (get_env_t , const __null_tail_receiver& __self) {
171+ return {};
172+ }
171173 };
172174
173175 struct __null_tail_sender {
174176 struct op {
177+ op () = default ;
178+ op (const op&) = delete ;
179+ op (op&&) = delete ;
180+ op& operator =(const op&) = delete ;
181+ op& operator =(op&&) = delete ;
182+
175183 // this is a nullable_tail_sender that always returns false to prevent
176184 // callers from calling start() and unwind()
177185 inline constexpr operator bool () const noexcept { return false ; }
186+
178187 friend void tag_invoke (start_t , op& self) noexcept {
179188 std::terminate ();
180189 }
@@ -187,7 +196,7 @@ namespace exec {
187196 using completion_signatures = completion_signatures<set_value_t (), set_stopped_t ()>;
188197
189198 template <class _TailReceiver >
190- friend auto tag_invoke (connect_t , __null_tail_sender&&, _TailReceiver&&)
199+ friend auto tag_invoke (connect_t , __null_tail_sender&&, _TailReceiver&&) noexcept
191200 -> op {
192201 return {};
193202 }
@@ -207,17 +216,40 @@ namespace exec {
207216 template <class _TailReceiver >
208217 struct op {
209218 using op_t = connect_result_t <_TailSender, _TailReceiver>;
210- explicit op () {}
211- explicit op (_TailSender __t , _TailReceiver __r) : op_(stdexec::connect(__t , __r)) {}
212- operator bool () const noexcept { return !!op_ && !!*op_; }
219+ op () = default ;
220+ op (const op&) = delete ;
221+ op (op&&) = delete ;
222+ op& operator =(const op&) = delete ;
223+ op& operator =(op&&) = delete ;
224+
225+ explicit op (_TailSender __t , _TailReceiver __r)
226+ : op_(stdexec::__conv{
227+ [&] {
228+ return stdexec::connect (__t , __r);
229+ }
230+ }) {}
231+ operator bool () const noexcept {
232+ if constexpr (__nullable_tail_sender_to<_TailSender, _TailReceiver>) {
233+ return !!op_ && !!*op_;
234+ } else {
235+ return !!op_;
236+ }
237+ }
213238
239+ [[nodiscard]]
214240 friend auto tag_invoke (start_t , op& __self) noexcept {
215- if (!__self.op_ || !*__self.op_ ) { std::terminate (); }
241+ if (!__self.op_ ) { std::terminate (); }
242+ if constexpr (__nullable_tail_sender_to<_TailSender, _TailReceiver>) {
243+ if (!*__self.op_ ) { std::terminate (); }
244+ }
216245 return stdexec::start (*__self.op_ );
217246 }
218247
219248 friend void tag_invoke (unwind_t , op& __self) noexcept {
220- if (!__self.op_ || !*__self.op_ ) { std::terminate (); }
249+ if (!__self.op_ ) { std::terminate (); }
250+ if constexpr (__nullable_tail_sender_to<_TailSender, _TailReceiver>) {
251+ if (!*__self.op_ ) { std::terminate (); }
252+ }
221253 exec::unwind (*__self.op_ );
222254 }
223255 std::optional<op_t > op_;
@@ -226,10 +258,11 @@ namespace exec {
226258 using completion_signatures = completion_signatures<set_value_t (), set_stopped_t ()>;
227259
228260 template <class _TailReceiver >
229- friend auto tag_invoke (connect_t , maybe_tail_sender&& __self, _TailReceiver&& __r)
261+ [[nodiscard]]
262+ friend auto tag_invoke (connect_t , maybe_tail_sender&& __self, _TailReceiver&& __r) noexcept
230263 -> op<_TailReceiver> {
231264 if (!__self.tail_sender_ ) { return {}; }
232- return { ((maybe_tail_sender&&)__self).tail_sender_ , __r};
265+ return op<_TailReceiver>{* ((maybe_tail_sender&&)__self).tail_sender_ , __r};
233266 }
234267
235268 template <class _Env >
@@ -241,4 +274,135 @@ namespace exec {
241274 private:
242275 std::optional<_TailSender> tail_sender_;
243276 };
277+
278+ template <tail_sender _TailSender, tail_receiver _TailReceiver = __null_tail_receiver>
279+ struct scoped_tail_sender {
280+ explicit scoped_tail_sender (_TailSender __t , _TailReceiver __r = _TailReceiver{}) noexcept
281+ : t_(__t )
282+ , r_(__r)
283+ , valid_(true ) {}
284+
285+ scoped_tail_sender (scoped_tail_sender&& other) noexcept
286+ : t_(other.s_)
287+ , r_(other.r_)
288+ , valid_(std::exchange(other.valid_, false )) {}
289+
290+ ~scoped_tail_sender () {
291+ if (valid_) {
292+ auto op = stdexec::connect (t_, r_);
293+ if constexpr (__nullable_tail_sender_to<_TailSender, _TailReceiver>) {
294+ if (!!op) {
295+ exec::unwind (op);
296+ }
297+ } else {
298+ exec::unwind (op);
299+ }
300+ }
301+ }
302+
303+ _TailSender get () noexcept { return t_; }
304+
305+ _TailSender release () noexcept {
306+ valid_ = false ;
307+ return t_;
308+ }
309+
310+ private:
311+ _TailSender t_;
312+ _TailReceiver r_;
313+ bool valid_;
314+ };
315+
316+ template <tail_sender _TailSender, tail_receiver _TailReceiver>
317+ auto __start_until_nullable (_TailSender __t , _TailReceiver __r) {
318+ if constexpr (__nullable_tail_sender_to<_TailSender, _TailReceiver>) {
319+ return __t ;
320+ } else if constexpr (__terminal_tail_sender_to<_TailSender, _TailReceiver>) {
321+ // restrict scope of op
322+ {
323+ auto op = stdexec::connect (std::move (__t ), std::move (__r));
324+ stdexec::start (op);
325+ }
326+ return __null_tail_sender{};
327+ } else {
328+ auto op = stdexec::connect (std::move (__t ), __r);
329+ return __start_until_nullable (stdexec::start (op), std::move (__r));
330+ }
331+ }
332+
333+ #if 0
334+ template <class _Next, class _TailSender, class _TailReceiver, class... _Prev>
335+ auto __start_next(_Next next, _TailReceiver r) {
336+ if constexpr (__one_of<_Next, _TailSender, _Prev...>) {
337+ static_assert(
338+ (nullable_tail_sender_to<_TailSender, _TailReceiver> ||
339+ (nullable_tail_sender_to<_Prev, _TailReceiver> || ...)),
340+ "At least one tail_sender in a cycle must be nullable to avoid "
341+ "entering an infinite loop");
342+ return __start_until_nullable(next, std::move(r));
343+ } else {
344+ using result_type =
345+ decltype(__start_sequential(next, r, type_list<_TailSender, _Prev...>{}));
346+ if constexpr (same_as<result_type, _Next>) {
347+ // Let the loop in resume_tail_sender() handle checking the boolean.
348+ return next;
349+ } else {
350+ return __start_sequential(next, std::move(r), type_list<_TailSender, _Prev...>{});
351+ }
352+ }
353+ }
354+
355+ template<tail_sender _TailSender, tail_receiver _TailReceiver, class... _Prev>
356+ auto _start_sequential(_TailSender c, _TailReceiver r, type_list<_Prev...>) {
357+ static_assert(
358+ _tail_sender<_TailSender>, "_start_sequential: must be called with a tail_sender");
359+ if constexpr (_terminal_tail_sender_to<_TailSender, _TailReceiver>) {
360+ if constexpr (nullable_tail_sender_to<_TailSender, _TailReceiver>) {
361+ return c;
362+ } else {
363+ // restrict scope of op
364+ {
365+ auto op = unifex::connect(std::move(c), std::move(r));
366+ unifex::start(op);
367+ }
368+ return null_tail_sender{};
369+ }
370+ } else {
371+ using next_t = next_tail_sender_to_t<_TailSender, _TailReceiver>;
372+ using result_type = decltype(_start_next<next_t, _TailSender, _TailReceiver, _Prev...>(
373+ std::declval<next_t>(), r));
374+ if constexpr (std::is_void_v<next_t>) {
375+ // restrict scope of op
376+ {
377+ auto op = unifex::connect(std::move(c), std::move(r));
378+ unifex::start(op);
379+ }
380+ return null_tail_sender{};
381+ } else if constexpr (same_as<result_type, next_t>) {
382+ auto op = unifex::connect(std::move(c), std::move(r));
383+ return unifex::start(op);
384+ } else if constexpr (nullable_tail_sender_to<_TailSender, _TailReceiver>) {
385+ auto op = unifex::connect(std::move(c), r);
386+ using result_type = variant_tail_sender<
387+ null_tail_sender,
388+ decltype(_start_next<next_t, _TailSender, _TailReceiver, _Prev...>(
389+ unifex::start(op), r))>;
390+ if (!op) {
391+ return result_type{null_tail_sender{}};
392+ }
393+ return result_type{
394+ _start_next<next_t, _TailSender, _TailReceiver, _Prev...>(unifex::start(op), r)};
395+ } else {
396+ auto op = unifex::connect(std::move(c), r);
397+ return _start_next<next_t, _TailSender, _TailReceiver, _Prev...>(unifex::start(op), r);
398+ }
399+ }
400+ }
401+
402+ template<class _TailSender, class _TailReceiver> //
403+ auto _start_sequential(_TailSender c, _TailReceiver r) {
404+ return _start_sequential(c, r, type_list<>{});
405+ }
406+ #endif
407+
244408} // namespace exec
0 commit comments