Skip to content

Commit d06fdef

Browse files
committed
add __start_until_nullable
1 parent 2d63fc5 commit d06fdef

File tree

2 files changed

+301
-15
lines changed

2 files changed

+301
-15
lines changed

include/exec/tail_sender.hpp

Lines changed: 178 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@
2121
namespace 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

Comments
 (0)