diff --git a/include/stdexec/__detail/__any.hpp b/include/stdexec/__detail/__any.hpp index af73c0d2d..9b36f791e 100644 --- a/include/stdexec/__detail/__any.hpp +++ b/include/stdexec/__detail/__any.hpp @@ -585,6 +585,27 @@ namespace STDEXEC::__any ////////////////////////////////////////////////////////////////////////////////////////// // __emplace_into + template + struct __dealloc_guard + { + _Alloc2 &__alloc; + typename std::allocator_traits<_Alloc2>::pointer __ptr; + bool __dismissed = false; + + constexpr void __dismiss() noexcept + { + __dismissed = true; + } + + constexpr ~__dealloc_guard() noexcept + { + if (!__dismissed) + { + std::allocator_traits<_Alloc2>::deallocate(__alloc, __ptr, 1); + } + } + }; + template constexpr _Model &__emplace_into([[maybe_unused]] _Allocator const &__alloc, [[maybe_unused]] __iroot *&__root_ptr, @@ -606,10 +627,10 @@ namespace STDEXEC::__any } else { - auto __alloc2 = STDEXEC::__rebind_allocator<_Model>(__alloc); - using __traits_t = std::allocator_traits; - auto *const __model = __traits_t::allocate(__alloc2, 1); - __scope_guard __guard{[&]() noexcept { __traits_t::deallocate(__alloc2, __model, 1); }}; + auto __alloc2 = STDEXEC::__rebind_allocator<_Model>(__alloc); + using __traits_t = std::allocator_traits; + auto *const __model = __traits_t::allocate(__alloc2, 1); + __dealloc_guard __guard{__alloc2, __model}; __traits_t::construct(__alloc2, __model, static_cast<_Args &&>(__args)...); __guard.__dismiss(); *__std::start_lifetime_as<__tagged_ptr>(__buff.data()) = __tagged_ptr(__model); diff --git a/include/stdexec/__detail/__completion_info.hpp b/include/stdexec/__detail/__completion_info.hpp index e846b7963..2b4d845d8 100644 --- a/include/stdexec/__detail/__completion_info.hpp +++ b/include/stdexec/__detail/__completion_info.hpp @@ -126,17 +126,19 @@ namespace STDEXEC } }(); + template + consteval auto __completion_sigs_splice(__indices<_Is...>) noexcept + { + return completion_signatures<__msplice<__sigs[_Is]>...>(); + } + template consteval auto __completion_sigs_from(_GetComplInfo) noexcept { constexpr auto __sigs = __completion_sigs_from_v<(_GetComplInfo())>; STDEXEC_IF_OK(__sigs) { - constexpr auto __fn = [=](__indices<_Is...>) - { - return completion_signatures<__msplice<__sigs[_Is]>...>(); - }; - return __fn(__make_indices<__sigs.size()>()); + return __completion_sigs_splice<__sigs>(__make_indices<__sigs.size()>()); } } diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 93f01cc4d..3363231c0 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -598,20 +598,31 @@ namespace STDEXEC return __transform(__signature<__msplice<_Info.__signature>>, _Info); }; - //! @tparam _Info A `__static_vector` of `__completion_info` objects representing - //! the completions of the predecessor sender. - template - static constexpr auto __get_cmpl_info_i = [](__indices<_Is...>) + template + struct __cmpl_info_inner_fn { - return [] + constexpr auto operator()() const { __static_vector<__completion_info, 0> __result; - // NB: this fold uses an overloaded addition operator that propagates - // __mexception objects when constexpr exceptions are not available. return (__maybe_transform_cmplsig<_Info[_Is]>(_Transform) + ... + __result); - }; + } }; + template + struct __cmpl_info_outer_fn + { + template + constexpr auto operator()(__indices<_Is...>) const + { + return __cmpl_info_inner_fn<_Info, _Transform, _Is...>{}; + } + }; + + //! @tparam _Info A `__static_vector` of `__completion_info` objects representing + //! the completions of the predecessor sender. + template + static constexpr auto __get_cmpl_info_i = __cmpl_info_outer_fn<_Info, _Transform>{}; + template struct __get_cmpl_info { diff --git a/include/stdexec/__detail/__memory.hpp b/include/stdexec/__detail/__memory.hpp index 8f26f6841..a499fb09d 100644 --- a/include/stdexec/__detail/__memory.hpp +++ b/include/stdexec/__detail/__memory.hpp @@ -94,14 +94,24 @@ namespace STDEXEC ///////////////////////////////////////////////////////////////////////////////////////// // __allocator_aware_forward: https://eel.is/c++draft/exec#snd.expos-49 template - [[nodiscard]] - constexpr auto __mk_obj_using_alloc_fn(_Alloc const &__alloc) noexcept + struct __obj_using_alloc_fn { - return [&__alloc](_Args &&...__args) + _Alloc const &__alloc_; + + template + constexpr auto operator()([[maybe_unused]] _Args &&...__args) const { return __tuple{ - std::make_obj_using_allocator<__decay_t<_Args>>(__alloc, static_cast<_Args &&>(__args))...}; - }; + std::make_obj_using_allocator<__decay_t<_Args>>(__alloc_, + static_cast<_Args &&>(__args))...}; + } + }; + + template + [[nodiscard]] + constexpr auto __mk_obj_using_alloc_fn(_Alloc const &__alloc) noexcept + { + return __obj_using_alloc_fn<_Alloc>{__alloc}; } template diff --git a/include/stdexec/__detail/__parallel_scheduler.hpp b/include/stdexec/__detail/__parallel_scheduler.hpp index 7a81b221a..7b81dde71 100644 --- a/include/stdexec/__detail/__parallel_scheduler.hpp +++ b/include/stdexec/__detail/__parallel_scheduler.hpp @@ -656,6 +656,19 @@ namespace STDEXEC return {__sched_}; } + template + struct __connect_fn + { + _Previous __previous; + __detail::__backend_ptr_t __sched; + + auto operator()(_Op& __op) && noexcept + { + using __receiver_t = _Op::__intermediate_receiver_t; + return STDEXEC::connect(std::move(__previous), __receiver_t{__op, std::move(__sched)}); + } + }; + /// Connects `__self` to `__rcvr`, returning the operation state containing the work to be done. template auto connect(_Rcvr __rcvr) && noexcept(__nothrow_move_constructible<_Rcvr>) @@ -664,14 +677,11 @@ namespace STDEXEC using __res_t = __detail::__system_bulk_op<_IsUnchunked, _Previous, _Size, _Fn, _Rcvr, _Parallelize>; using __receiver_t = __res_t::__intermediate_receiver_t; - return {std::move(*this), - std::move(__rcvr), - [this](auto& __op) - { - // Connect bulk input receiver with the previous operation and store in the operating state. - return STDEXEC::connect(std::move(this->__previous_), - __receiver_t{__op, std::move(this->__sched_)}); - }}; + return { + std::move(*this), + std::move(__rcvr), + __connect_fn<__res_t>{std::move(__previous_), std::move(__sched_)} + }; } /// Gets the completion signatures for this sender. diff --git a/include/stdexec/__detail/__receivers.hpp b/include/stdexec/__detail/__receivers.hpp index 8c317c7e3..75cc603a8 100644 --- a/include/stdexec/__detail/__receivers.hpp +++ b/include/stdexec/__detail/__receivers.hpp @@ -457,12 +457,21 @@ namespace STDEXEC } template - constexpr auto __mk_completion_fn(_Tag, _Receiver &__rcvr) noexcept + struct __completion_fn { - return [&](_Args &&...__args) noexcept + _Receiver &__rcvr_; + + template + constexpr void operator()(_Args &&...__args) const noexcept { - _Tag()(static_cast<_Receiver &&>(__rcvr), static_cast<_Args &&>(__args)...); - }; + _Tag()(static_cast<_Receiver &&>(__rcvr_), static_cast<_Args &&>(__args)...); + } + }; + + template + constexpr auto __mk_completion_fn(_Tag, _Receiver &__rcvr) noexcept + { + return __completion_fn<_Tag, _Receiver>{__rcvr}; } // Used to test whether a sender has a nothrow connect to a receiver whose environment diff --git a/include/stdexec/__detail/__tuple.hpp b/include/stdexec/__detail/__tuple.hpp index 4374c9f45..7d9ddcf61 100644 --- a/include/stdexec/__detail/__tuple.hpp +++ b/include/stdexec/__detail/__tuple.hpp @@ -453,15 +453,27 @@ namespace STDEXEC // namespace __tup { + template + struct __apply_partial_t + { + _Tuple&& __tup; + _Fn __fn; + + template + STDEXEC_ATTRIBUTE(host, device, always_inline) + constexpr auto operator()(_Us&&... __us) noexcept(__nothrow_applicable<_Fn, _Tuple, _Us...>) + -> __apply_result_t<_Fn, _Tuple, _Us...> + { + return STDEXEC::__apply(__fn, static_cast<_Tuple&&>(__tup), static_cast<_Us&&>(__us)...); + } + }; + template <__is_tuple _Tuple, class _Fn> STDEXEC_ATTRIBUTE(host, device, always_inline) constexpr auto operator%(_Tuple&& __tup, _Fn __fn) noexcept(__nothrow_move_constructible<_Fn>) + -> __apply_partial_t<_Fn, _Tuple> { - return [&__tup, __fn = static_cast<_Fn&&>(__fn)](_Us&&... __us) noexcept( - __nothrow_applicable<_Fn, _Tuple, _Us...>) -> __apply_result_t<_Fn, _Tuple, _Us...> - { - return STDEXEC::__apply(__fn, static_cast<_Tuple&&>(__tup), static_cast<_Us&&>(__us)...); - }; + return __apply_partial_t<_Fn, _Tuple>{static_cast<_Tuple&&>(__tup), static_cast<_Fn&&>(__fn)}; } struct __cat_apply_t diff --git a/include/stdexec/__detail/__variant.hpp b/include/stdexec/__detail/__variant.hpp index cbe5cab04..a772d5fe9 100644 --- a/include/stdexec/__detail/__variant.hpp +++ b/include/stdexec/__detail/__variant.hpp @@ -298,6 +298,27 @@ namespace STDEXEC return *std::launder(__ptr); } + template + struct __emplace_from_fn + { + _Fn &__fn_; + std::tuple<_As &...> __as_; + + static constexpr bool __is_nothrow = __nothrow_callable<_Fn, _As...>; + + template + constexpr auto + __call(std::index_sequence<_Size...>) const noexcept(__is_nothrow) -> decltype(auto) + { + return static_cast<_Fn &&>(__fn_)(static_cast<_As &&>(std::get<_Size>(__as_))...); + } + + constexpr auto operator()() const noexcept(__is_nothrow) -> decltype(auto) + { + return __call(std::index_sequence_for<_As...>{}); + } + }; + template STDEXEC_ATTRIBUTE(host, device) constexpr auto __emplace_from(_Fn &&__fn, _As &&...__as) @@ -314,9 +335,7 @@ namespace STDEXEC { auto *__ptr = std::construct_at<__value_t>( static_cast<__value_t *>(__data()), - STDEXEC::__emplace_from( - [&]() noexcept(__is_nothrow) -> decltype(auto) - { return static_cast<_Fn &&>(__fn)(static_cast<_As &&>(__as)...); })); + STDEXEC::__emplace_from(__emplace_from_fn<_Fn, _As...>{__fn, {__as...}})); __sg.__dismiss(); return *std::launder(__ptr); } diff --git a/include/stdexec/coroutine.hpp b/include/stdexec/coroutine.hpp index 1ea3759db..092d0ca5c 100644 --- a/include/stdexec/coroutine.hpp +++ b/include/stdexec/coroutine.hpp @@ -88,6 +88,17 @@ namespace STDEXEC public: constexpr __coroutine_handle() = default; + template + static constexpr auto __stopped_callback(void* __address) noexcept -> __std::coroutine_handle<> + { + // This causes the rest of the coroutine (the part after the co_await + // of the sender) to be skipped and invokes the calling coroutine's + // stopped handler. + return __std::coroutine_handle<_Promise>::from_address(__address) + .promise() + .unhandled_stopped(); + } + template constexpr __coroutine_handle(__std::coroutine_handle<_Promise> __coro) noexcept : __std::coroutine_handle<>(__coro) @@ -98,15 +109,7 @@ namespace STDEXEC if constexpr (__has_unhandled_stopped) { - __stopped_callback_ = [](void* __address) noexcept -> __std::coroutine_handle<> - { - // This causes the rest of the coroutine (the part after the co_await - // of the sender) to be skipped and invokes the calling coroutine's - // stopped handler. - return __std::coroutine_handle<_Promise>::from_address(__address) - .promise() - .unhandled_stopped(); - }; + __stopped_callback_ = &__stopped_callback<_Promise>; } // If _Promise doesn't implement unhandled_stopped(), then if a "stopped" unwind // reaches this point, it's considered an unhandled exception and terminate()