Skip to content

Commit 7815a8f

Browse files
exec::repeat_until et al.: Dependent Child Sender Support (#1879)
* exec::repeat_until et al.: Dependent Child Sender Support The TODO eliminated by this commit not only meant that exec:: repeat_until et al. previously didn't the usual constant evaluation techniques to report errors, it also meant that a hard compile error (rather than a return type indicating that the sender is dependent) resulted if: - The child sender was dependent, and - An attempt was made to determine the completion signatures of the parent without an environment Updated so exec::repeat_until et al. now properly report that they are dependent if their child is dependent. --------- Co-authored-by: Eric Niebler <eniebler@nvidia.com>
1 parent 78ae16b commit 7815a8f

5 files changed

Lines changed: 140 additions & 75 deletions

File tree

include/exec/repeat_until.hpp

Lines changed: 95 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@
2121
#include "../stdexec/__detail/__optional.hpp"
2222
#include "../stdexec/execution.hpp"
2323

24+
#include "completion_signatures.hpp"
2425
#include "sequence.hpp"
2526
#include "trampoline_scheduler.hpp"
2627

2728
#include <exception>
2829
#include <type_traits>
2930

31+
STDEXEC_PRAGMA_PUSH()
32+
STDEXEC_PRAGMA_IGNORE_EDG(not_used_in_template_function_params)
33+
3034
namespace experimental::execution
3135
{
36+
struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_;
37+
struct _EXPECTING_A_SENDER_OF_VOID_;
38+
3239
namespace __repeat
3340
{
3441
using namespace STDEXEC;
@@ -81,8 +88,7 @@ namespace experimental::execution
8188
else
8289
{
8390
// Mixed results:
84-
constexpr bool __is_nothrow = noexcept(
85-
(static_cast<bool>(static_cast<_Booleans &&>(__bools)) && ...));
91+
constexpr bool __is_nothrow = (std::is_nothrow_convertible_v<_Booleans, bool> && ...);
8692
STDEXEC_TRY
8793
{
8894
// If the child sender completed with true, we're done
@@ -110,11 +116,11 @@ namespace experimental::execution
110116

111117
template <class _Error>
112118
constexpr void set_error(_Error &&__err) noexcept
113-
{ // intentionally pass-by-value
119+
{
114120
STDEXEC_TRY
115121
{
116122
auto __err_copy = static_cast<_Error &&>(__err); // make a local copy of the error...
117-
__state_->__cleanup(); // because this could potentially invalidate it.
123+
__state_->__cleanup(); // ... because this could potentially invalidate it.
118124
STDEXEC::set_error(std::move(__state_->__rcvr_), static_cast<_Error &&>(__err_copy));
119125
}
120126
STDEXEC_CATCH_ALL
@@ -134,9 +140,9 @@ namespace experimental::execution
134140
}
135141

136142
[[nodiscard]]
137-
constexpr auto get_env() const noexcept -> env_of_t<_Receiver>
143+
constexpr auto get_env() const noexcept -> __fwd_env_t<env_of_t<_Receiver>>
138144
{
139-
return STDEXEC::get_env(__state_->__rcvr_);
145+
return __fwd_env(STDEXEC::get_env(__state_->__rcvr_));
140146
}
141147

142148
__opstate_base<_Receiver> *__state_;
@@ -207,69 +213,97 @@ namespace experimental::execution
207213

208214
STDEXEC_PRAGMA_POP()
209215

210-
struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_
211-
{};
212-
struct _EXPECTING_A_SENDER_OF_VOID_
213-
{};
214-
215-
template <class _Child, class... _Args>
216-
using __values_t =
217-
// There's something funny going on with __if_c here. Use std::conditional_t instead. :-(
218-
std::conditional_t<
219-
((sizeof...(_Args) == 1) && (__std::convertible_to<_Args, bool> && ...)),
220-
std::conditional_t<(__bool_constant<_Args, false> && ...),
221-
completion_signatures<>,
222-
completion_signatures<set_value_t()>>,
223-
__mexception<_WHAT_(_INVALID_ARGUMENT_),
224-
_WHERE_(_IN_ALGORITHM_, repeat_until_t),
225-
_WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_),
226-
_WITH_PRETTY_SENDER_<_Child>>>;
227-
228-
template <class... _Booleans>
229-
using __values_overload_nothrow_bool_convertible_t =
230-
__mand<std::is_nothrow_convertible<_Booleans, bool>...>;
231-
232-
template <class _Sender, class... _Env>
233-
using __values_nothrow_bool_convertible_t =
234-
__value_types_t<__completion_signatures_of_t<_Sender, _Env...>, // sigs
235-
__qq<__values_overload_nothrow_bool_convertible_t>, // tuple
236-
__qq<__mand> // variant
237-
>;
238-
239-
template <typename _Sender, typename... _Env>
240-
using __with_eptr_completion_t = __eptr_completion_unless<
241-
__values_nothrow_bool_convertible_t<_Sender, _Env...>::value
242-
&& __cmplsigs::__partitions_of_t<
243-
__completion_signatures_of_t<_Sender, _Env...>>::__nothrow_decay_copyable::__errors::value
244-
&& (__nothrow_connectable<_Sender, __receiver_archetype<_Env>> && ...)>;
245-
246-
template <class...>
247-
using __delete_set_value_t = completion_signatures<>;
248-
249-
template <class _Child, class... _Env>
250-
using __completions_t = STDEXEC::transform_completion_signatures<
251-
__completion_signatures_of_t<__decay_t<_Child> &, _Env...>,
252-
STDEXEC::transform_completion_signatures<
253-
__completion_signatures_of_t<STDEXEC::schedule_result_t<trampoline_scheduler>, _Env...>,
254-
__with_eptr_completion_t<_Child, _Env...>,
255-
__delete_set_value_t>,
256-
__mbind_front_q<__values_t, _Child>::template __f>;
257-
258216
struct __repeat_until_impl : __sexpr_defaults
259217
{
218+
template <class _Child>
219+
static constexpr auto __transform_values = []<class... _Args>()
220+
{
221+
if constexpr (sizeof...(_Args) != 1 || (!__std::convertible_to<_Args, bool> || ...))
222+
{
223+
return exec::throw_compile_time_error<
224+
_WHAT_(_INVALID_ARGUMENT_),
225+
_WHERE_(_IN_ALGORITHM_, repeat_until_t),
226+
_WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_),
227+
_WITH_PRETTY_SENDER_<_Child>>();
228+
}
229+
else if constexpr ((__bool_constant<_Args, false> && ...))
230+
{
231+
return STDEXEC::completion_signatures{};
232+
}
233+
else if constexpr ((std::is_nothrow_convertible_v<_Args, bool> && ...))
234+
{
235+
return STDEXEC::completion_signatures<set_value_t()>();
236+
}
237+
else
238+
{
239+
return STDEXEC::completion_signatures<set_value_t(), set_error_t(std::exception_ptr)>();
240+
}
241+
};
242+
243+
static constexpr auto __transform_errors = []<class _Error>() noexcept
244+
{
245+
if constexpr (__nothrow_decay_copyable<_Error> || __decays_to<_Error, std::exception_ptr>)
246+
{
247+
return STDEXEC::completion_signatures<set_error_t(_Error)>();
248+
}
249+
else
250+
{
251+
return STDEXEC::completion_signatures<set_error_t(_Error),
252+
set_error_t(std::exception_ptr)>();
253+
}
254+
};
255+
260256
template <class _Sender, class... _Env>
261257
static consteval auto __get_completion_signatures()
262258
{
263-
// TODO: port this to use constant evaluation
264-
return __completions_t<__child_of<_Sender>, _Env...>{};
259+
using __child_t = __child_of<_Sender>;
260+
using __bouncer_t = schedule_result_t<trampoline_scheduler>;
261+
using __eptr_completion_t = set_error_t(std::exception_ptr);
262+
constexpr auto __eptr_completion = (__eptr_completion_t *) nullptr;
263+
264+
STDEXEC_COMPLSIGS_LET(
265+
__sigs,
266+
exec::transform_completion_signatures(get_completion_signatures<__child_t, _Env...>(),
267+
__transform_values<__child_t>,
268+
__transform_errors))
269+
{
270+
// The repeat_until sender is a dependent sender if one of the following is
271+
// true:
272+
// - the child sender is a dependent sender, or
273+
// - the trampoline scheduler's sender is a dependent sender, or
274+
// - sizeof...(_Env) == 0 and the child sender does not have a
275+
// set_error(exception_ptr) completion.
276+
constexpr bool __is_dependent = (sizeof...(_Env) == 0)
277+
&& (dependent_sender<__bouncer_t>
278+
|| !__sigs.__contains(__eptr_completion));
279+
if constexpr (__is_dependent)
280+
{
281+
return exec::throw_compile_time_error<dependent_sender_error,
282+
_WITH_PRETTY_SENDER_<__child_t>>();
283+
}
284+
else
285+
{
286+
constexpr bool __has_nothrow_connect =
287+
(__nothrow_connectable<__child_t, __receiver_archetype<_Env>> || ...);
288+
constexpr auto __eptr_sigs = __eptr_completion_unless<__has_nothrow_connect>();
289+
constexpr auto __bouncer_sigs = exec::transform_completion_signatures(
290+
get_completion_signatures<__bouncer_t, _Env...>(),
291+
exec::ignore_completion()); // drop the set_value_t() completion from the
292+
// trampoline scheduler.
293+
294+
return exec::concat_completion_signatures(__sigs, __eptr_sigs, __bouncer_sigs);
295+
}
296+
}
265297
};
266298

267299
static constexpr auto __connect =
268300
[]<class _Sender, class _Receiver>(_Sender &&__sndr, _Receiver __rcvr) noexcept(
269-
noexcept(__opstate(STDEXEC::__get<2>(__declval<_Sender>()), __declval<_Receiver>())))
301+
__nothrow_constructible_from<__opstate<__child_of<_Sender>, _Receiver>,
302+
__child_of<_Sender>,
303+
_Receiver>)
270304
{
271-
return __opstate(STDEXEC::__get<2>(static_cast<_Sender &&>(__sndr)),
272-
static_cast<_Receiver &&>(__rcvr));
305+
auto &[__tag, __ign, __child] = __sndr;
306+
return __opstate(STDEXEC::__forward_like<_Sender>(__child), std::move(__rcvr));
273307
};
274308
};
275309

@@ -362,3 +396,5 @@ namespace STDEXEC
362396
struct __sexpr_impl<exec::repeat_until_t> : exec::__repeat::__repeat_until_impl
363397
{};
364398
} // namespace STDEXEC
399+
400+
STDEXEC_PRAGMA_POP()

include/exec/sequence.hpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ namespace experimental::execution
3737
{
3838
template <class Sender>
3939
STDEXEC_ATTRIBUTE(nodiscard, host, device)
40-
constexpr auto operator()(Sender sndr) const -> Sender;
40+
constexpr auto operator()(Sender sndr) const
41+
noexcept(STDEXEC::__nothrow_move_constructible<Sender>) -> Sender;
4142

4243
template <class... Senders>
4344
requires(sizeof...(Senders) > 1)
4445
STDEXEC_ATTRIBUTE(nodiscard, host, device)
45-
constexpr auto operator()(Senders... sndrs) const -> _sndr<Senders...>;
46+
constexpr auto operator()(Senders... sndrs) const
47+
noexcept(STDEXEC::__nothrow_move_constructible<Senders...>) -> _sndr<Senders...>;
4648
};
4749

4850
template <class Rcvr>
@@ -334,15 +336,17 @@ namespace experimental::execution
334336

335337
template <class Sender>
336338
STDEXEC_ATTRIBUTE(host, device)
337-
constexpr auto sequence_t::operator()(Sender sndr) const -> Sender
339+
constexpr auto sequence_t::operator()(Sender sndr) const
340+
noexcept(STDEXEC::__nothrow_move_constructible<Sender>) -> Sender
338341
{
339342
return sndr;
340343
}
341344

342345
template <class... Senders>
343346
requires(sizeof...(Senders) > 1)
344347
STDEXEC_ATTRIBUTE(host, device)
345-
constexpr auto sequence_t::operator()(Senders... sndrs) const -> _sndr<Senders...>
348+
constexpr auto sequence_t::operator()(Senders... sndrs) const
349+
noexcept(STDEXEC::__nothrow_move_constructible<Senders...>) -> _sndr<Senders...>
346350
{
347351
return _sndr<Senders...>{{}, {}, {static_cast<Senders&&>(sndrs)...}};
348352
}

include/stdexec/__detail/__receivers.hpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,10 @@ namespace STDEXEC
250250
};
251251
}
252252

253-
template <class _Env>
254-
struct __receiver_archetype
253+
// Used to test whether a sender has a nothrow connect to a receiver whose environment
254+
// is _Env..., or if _Env... is empty (indicating that the sender is non-dependent), to
255+
// a receiver with an arbitrary environment.
256+
struct __receiver_archetype_base
255257
{
256258
using receiver_concept = receiver_t;
257259

@@ -267,9 +269,13 @@ namespace STDEXEC
267269

268270
STDEXEC_ATTRIBUTE(host, device)
269271
constexpr void set_stopped() noexcept {}
272+
};
270273

274+
template <class _Env>
275+
struct __receiver_archetype : __receiver_archetype_base
276+
{
271277
STDEXEC_ATTRIBUTE(nodiscard, noreturn, host, device)
272-
_Env get_env() const noexcept
278+
auto get_env() const noexcept -> _Env
273279
{
274280
STDEXEC_ASSERT(false);
275281
STDEXEC_TERMINATE();

include/stdexec/__detail/__transform_completion_signatures.hpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,24 +325,24 @@ namespace STDEXEC
325325
template <class _Fn, class... _Args>
326326
using __transform_result_t = decltype(__declval<_Fn>().template operator()<_Args...>());
327327

328-
template <class _SetTag, class... _Args, class _Fn>
328+
template <class... _Args, class _Fn>
329329
[[nodiscard]]
330330
consteval auto
331-
__transform_expr(_Fn const &__fn) -> __transform_result_t<_Fn const &, _SetTag, _Args...>
331+
__transform_expr(_Fn const &__fn, int) -> __transform_result_t<_Fn const &, _Args...>
332332
{
333-
return __fn.template operator()<_SetTag, _Args...>();
333+
return __fn.template operator()<_Args...>();
334334
}
335335

336336
template <class _Fn>
337337
[[nodiscard]]
338-
consteval auto __transform_expr(_Fn const &__fn) -> __call_result_t<_Fn const &>
338+
consteval auto __transform_expr(_Fn const &__fn, long) -> __call_result_t<_Fn const &>
339339
{
340340
return __fn();
341341
}
342342

343343
template <class _Fn, class... _Args>
344-
using __transform_expr_t = decltype(__cmplsigs::__transform_expr<_Args...>(
345-
__declval<_Fn const &>()));
344+
using __transform_expr_t =
345+
decltype(__cmplsigs::__transform_expr<_Args...>(__declval<_Fn const &>(), 0));
346346

347347
// transform_completion_signatures:
348348
template <class... _Args, class _Fn>
@@ -354,11 +354,11 @@ namespace STDEXEC
354354
using __completions_t = __transform_expr_t<_Fn, _Args...>;
355355
if constexpr (__well_formed_completions<__completions_t>)
356356
{
357-
return __cmplsigs::__transform_expr<_Args...>(__fn);
357+
return __cmplsigs::__transform_expr<_Args...>(__fn, 0);
358358
}
359359
else
360360
{
361-
(void) __cmplsigs::__transform_expr<_Args...>(__fn); // potentially throwing
361+
(void) __cmplsigs::__transform_expr<_Args...>(__fn, 0); // potentially throwing
362362
return STDEXEC::__throw_compile_time_error<
363363
_IN_TRANSFORM_COMPLETION_SIGNATURES_,
364364
_A_TRANSFORM_FUNCTION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION_,

test/exec/test_repeat_until.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
#include <catch2/catch.hpp>
2929

3030
#include <concepts>
31+
#include <cstddef>
3132
#include <limits>
3233
#include <memory>
3334
#include <stdexcept>
35+
#include <type_traits>
3436
#include <utility>
3537

3638
namespace ex = STDEXEC;
@@ -315,7 +317,7 @@ namespace
315317
static_assert(
316318
std::same_as<ex::error_types_of_t<decltype(only_stopped)>, ex::__detail::__not_a_variant>,
317319
"Expect no value completions");
318-
static_assert(ex::sender_of<decltype(only_stopped), ex::set_stopped_t()>,
320+
static_assert(ex::sender_of<decltype(only_stopped), ex::set_stopped_t(), ex::env<>>,
319321
"Missing set_stopped_t() from upstream");
320322

321323
// operator| and sync_wait require valid completion signatures
@@ -465,4 +467,21 @@ namespace
465467
"Missing added set_error_t(std::exception_ptr)");
466468
}
467469
}
470+
471+
TEST_CASE("repeat_until works with a dependent sender", "[adaptors][repeat_until]")
472+
{
473+
std::size_t invoked = 0;
474+
auto snd = exec::repeat_until(ex::read_env(ex::get_stop_token)
475+
| ex::then(
476+
[&](auto token) noexcept
477+
{
478+
++invoked;
479+
return std::is_same_v<ex::never_stop_token, decltype(token)>;
480+
}));
481+
static_assert(ex::dependent_sender<decltype(snd)>);
482+
auto op = ex::connect(std::move(snd), expect_void_receiver{});
483+
CHECK(!invoked);
484+
ex::start(op);
485+
CHECK(invoked == 1);
486+
}
468487
} // namespace

0 commit comments

Comments
 (0)