Last active
February 2, 2025 00:11
-
-
Save ericniebler/0896776ab1c8f5b7f77d7094c0400df5 to your computer and use it in GitHub Desktop.
Better living through constexpr exceptions: a std::execution implementation with constexpr completion signature computation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <algorithm> | |
#include <coroutine> | |
#include <iostream> | |
#include <functional> | |
#include <optional> | |
#include <string_view> | |
#include <tuple> | |
#include <typeinfo> | |
#include <type_traits> | |
#include <utility> | |
// #region General utilities, not related to std::execution | |
// Preprocessing help until clang implements "structured binding can introduce a pack" | |
//////////////////////////////////////////////////////////////////////////////// | |
// _FOR_EACH | |
// Inspired by "Recursive macros with C++20 __VA_OPT__", by David Mazières | |
// https://www.scs.stanford.edu/~dm/blog/va-opt.html | |
#define _EXPAND(...) __VA_ARGS__ | |
#define _EXPAND_R(...) \ | |
_EXPAND_R1(_EXPAND_R1(_EXPAND_R1(_EXPAND_R1(__VA_ARGS__)))) \ | |
/**/ | |
#define _EXPAND_R1(...) \ | |
_EXPAND_R2(_EXPAND_R2(_EXPAND_R2(_EXPAND_R2(__VA_ARGS__)))) \ | |
/**/ | |
#define _EXPAND_R2(...) \ | |
_EXPAND_R3(_EXPAND_R3(_EXPAND_R3(_EXPAND_R3(__VA_ARGS__)))) \ | |
/**/ | |
#define _EXPAND_R3(...) \ | |
_EXPAND(_EXPAND(_EXPAND(_EXPAND(__VA_ARGS__)))) \ | |
/**/ | |
#define _PARENS () | |
#define _FOR_EACH(_MACRO, ...) \ | |
__VA_OPT__(_EXPAND_R(_FOR_EACH_HELPER(_MACRO, __VA_ARGS__))) \ | |
/**/ | |
#define _FOR_EACH_HELPER(_MACRO, _A1, ...) \ | |
_MACRO(_A1) __VA_OPT__(_FOR_EACH_AGAIN _PARENS(_MACRO, __VA_ARGS__)) /**/ | |
#define _FOR_EACH_AGAIN() _FOR_EACH_HELPER | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// General utilities | |
template <class..., class... Ts> | |
[[deprecated]] void print(Ts&&...) {} | |
template <class T> | |
using _declval = T&& (*)() noexcept; | |
template <class T> | |
using _identity_t = T; | |
template <class T> | |
using _add_const_t = const T; | |
template <class T> | |
using _add_lvalue_ref_t = T&; | |
template <class T> | |
using _add_rvalue_ref_t = T&&; | |
template <template <class> class C> | |
struct _mquote1 { | |
template <class T> | |
using _apply = C<T>; | |
}; | |
template <template <class> class... Cs> | |
struct _mcompose1; | |
template <> | |
struct _mcompose1<> : _mquote1<_identity_t> { }; | |
template <template <class> class C> | |
struct _mcompose1<C> : _mquote1<C> { }; | |
template <template <class> class C0, template <class> class C1> | |
struct _mcompose1<C0, C1> { | |
template <class T> | |
using _apply = C0<C1<T>>; | |
}; | |
template <class Fn, class... Ts> | |
using _mapply = Fn::template _apply<Ts...>; | |
template <class T> extern _mquote1<_identity_t> _copy_cvref_v; | |
template <class T> extern _mquote1<_add_const_t> _copy_cvref_v<T const>; | |
template <class T> extern _mquote1<_add_lvalue_ref_t> _copy_cvref_v<T&>; | |
template <class T> extern _mquote1<_add_rvalue_ref_t> _copy_cvref_v<T&&>; | |
template <class T> extern _mcompose1<_add_lvalue_ref_t, _add_const_t> _copy_cvref_v<T const&>; | |
template <class T> extern _mcompose1<_add_rvalue_ref_t, _add_const_t> _copy_cvref_v<T const&&>; | |
template <class From, class To> | |
using _copy_cvref_t = _mapply<decltype(_copy_cvref_v<From>), To>; | |
template <size_t I> | |
using _size_t = std::integral_constant<size_t, I>; | |
template <class T, template <class...> class> | |
inline constexpr bool _is_specialization_of_helper = false; | |
template <class... Ts, template <class...> class C> | |
inline constexpr bool _is_specialization_of_helper<C<Ts...>, C> = true; | |
template <class T, template <class...> class C> | |
concept _is_specialization_of = _is_specialization_of_helper<T, C>; | |
template <template <class...> class C, class... Ts> | |
concept _can_be_instantiated_with = requires { | |
typename C<Ts...>; | |
}; | |
template <class A, class B> | |
concept _decays_to = std::same_as<std::decay_t<A>, B>; | |
template <auto> | |
concept _is_constant = true; | |
template <class... Ts> | |
concept _decay_copyable = requires (Ts&&(*...ts)()) { | |
(auto(ts()), ...); | |
}; | |
template <class... Ts> | |
concept _nothrow_decay_copyable = requires (Ts&&(*...ts)()) { | |
{(auto(ts()), ...)} noexcept; | |
}; | |
template <class Fn, class... Ts> | |
concept _callable_with = requires (Fn&& fn, Ts&&... ts) { | |
std::forward<Fn>(fn)(std::forward<Ts>(ts)...); | |
}; | |
template <class Fn, class... Ts> | |
concept _nothrow_callable_with = requires (Fn&& fn, Ts&&... ts) { | |
{ std::forward<Fn>(fn)(std::forward<Ts>(ts)...) } noexcept; | |
}; | |
template <class Fn, class... Ts> | |
concept _meta_callable_with = requires (Fn&& fn) { | |
std::forward<Fn>(fn).template operator()<Ts...>(); | |
}; | |
template <class Fn, class... Ts> | |
using _call_result_t = decltype(std::declval<Fn>()(std::declval<Ts>()...)); | |
struct _none_such {}; | |
struct _ignore { | |
_ignore() = default; | |
constexpr _ignore(auto&&, auto&&...) noexcept { } | |
constexpr const _ignore& operator=(auto&&) const noexcept { | |
return *this; | |
} | |
}; | |
template <auto Constant> | |
struct _constant_wrapper { | |
using type = decltype(Constant); | |
static constexpr auto value = Constant; | |
}; | |
// efficient std::conditional and std::enable_if | |
template <bool Cond, class... Ts> | |
using _if_helper = Ts...[!Cond]; | |
template <bool Cond, class Then = void, class... Else> | |
using _if = _if_helper<Cond, Then, Else...>; | |
// _typelist: a list of types | |
template <class... Ts> | |
struct _typelist { | |
template <template <class...> class C> | |
using _apply = C<Ts...>; | |
}; | |
// _typeset: a set of unique types | |
template <class T> | |
struct _typeset_element | |
{ | |
_typeset_element() = default; | |
constexpr explicit _typeset_element(T) noexcept { } | |
}; | |
template <class...> | |
struct _typeset : _typeset_element<_ignore> { | |
template <class... Ts> | |
constexpr size_t size(this _typeset<Ts...>) noexcept { | |
return sizeof...(Ts); | |
} | |
template <class... Ts, class U> | |
constexpr auto operator+(this _typeset<Ts...> set, _typeset_element<U>) noexcept { | |
if constexpr (__is_base_of(_typeset_element<U>, _typeset<Ts...>)) { | |
return set; | |
} else { | |
return _typeset<U, Ts...>(); | |
} | |
} | |
template <class... Ts, template <class...> class C, class... Us> | |
constexpr auto operator+(this _typeset<Ts...> set, C<Us...>) noexcept { | |
return (set + ... + _typeset_element<Us>()); | |
} | |
template <class... Ts, _meta_callable_with<Ts...> Fn> | |
constexpr decltype(auto) _apply(this _typeset<Ts...>, Fn fn) { | |
return fn.template operator()<Ts...>(); | |
} | |
}; | |
template <class T, class... Ts> | |
struct _typeset<T, Ts...> | |
: _typeset_element<T> | |
, _typeset<Ts...> | |
{ }; | |
template <class... Ts> | |
using _mk_typeset = decltype((_typeset() +...+ _typeset_element<Ts>())); | |
// A simple structural tuple type | |
template <class T, size_t I> | |
struct _tuple_element { | |
T value; | |
}; | |
template <auto, class... Ts> | |
struct _tupl; | |
template <size_t... Is, std::index_sequence<Is...>* Indices, class... Ts> | |
struct _tupl<Indices, Ts...> : _tuple_element<Ts, Is>... { | |
template <template <class...> class Fn> | |
using _apply = Fn<Ts...>; | |
template <size_t I, class Self> | |
requires (I < sizeof...(Ts)) | |
constexpr decltype(auto) get(this Self&& self) noexcept { | |
return (std::forward<Self>(self)._tuple_element<Ts...[I], I>::value); | |
} | |
template <class Self, _callable_with<_copy_cvref_t<Self, Ts>...> Fn> | |
constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(_nothrow_callable_with<Fn, _copy_cvref_t<Self, Ts>...>) { | |
return std::forward<Fn>(fn)(std::forward<Self>(self)._tuple_element<Ts, Is>::value...); | |
} | |
}; | |
template <class... Ts> | |
_tupl(Ts...) -> _tupl<(std::index_sequence_for<Ts...>*) nullptr, Ts...>; | |
template <class... Ts> | |
using _tuple = _tupl<(std::index_sequence_for<Ts...>*) nullptr, Ts...>; | |
// Generic utilities for tuple-like types | |
constexpr size_t _max_structured_tuple_size = 16; | |
namespace _apply_cpo { | |
template <class Fn> | |
struct _std_apply_probe { | |
template <class... Ts> | |
constexpr auto operator()(Ts&&...) const noexcept { | |
return std::bool_constant<_callable_with<Fn, Ts...>>(); | |
} | |
}; | |
template <class Fn, class Tupl> | |
concept _member_apply = requires (_declval<Fn> fn, _declval<Tupl> tup) { | |
tup().apply(fn()); | |
}; | |
template <class Fn, class Tupl> | |
concept _non_member_apply = requires (_declval<Tupl> tup) { | |
{ apply(_std_apply_probe<Fn>(), tup()) } -> std::same_as<std::true_type>; | |
}; | |
struct _fn { | |
template <class Fn, class Tupl> | |
static constexpr bool _nothrow(_declval<Fn> fn = {}, _declval<Tupl> tupl = {}) noexcept { | |
if constexpr (_member_apply<Fn, Tupl>) { | |
return noexcept(tupl().apply(fn())); | |
} | |
else if constexpr (_non_member_apply<Fn, Tupl>) { | |
return noexcept(apply(fn(), tupl())); | |
} | |
return true; | |
} | |
template <class Fn, class Tupl> | |
requires _member_apply<Fn, Tupl> || _non_member_apply<Fn, Tupl> | |
constexpr decltype(auto) operator()(Fn&& fn, Tupl&& tup) const noexcept(_nothrow<Fn, Tupl>()) { | |
if constexpr (_member_apply<Fn, Tupl>) { | |
return std::forward<Tupl>(tup).apply(std::forward<Fn>(fn)); | |
} | |
else { | |
return apply(std::forward<Fn>(fn), std::forward<Tupl>(tup)); | |
} | |
} | |
}; | |
} | |
struct _count_fn { | |
constexpr auto operator()(auto&&... ts) const noexcept { | |
return _size_t<sizeof...(ts)>(); | |
} | |
}; | |
struct _any { | |
template <class T> | |
constexpr operator T&& () const; | |
}; | |
template <class T> | |
struct _any_base { | |
template <class U, class To = std::remove_cvref_t<U>> | |
requires std::derived_from<T, To> && std::is_empty_v<To> | |
constexpr operator U&&() const; | |
}; | |
// A "structured" tuple-like type is an ordinary struct that is usable as the | |
// initializer for a structured binding. There is no way in standard C++ to | |
// detect such types. Below we approximate it using the "magic tuple" trick. | |
// This code is easily confused. | |
template <class> | |
constexpr size_t _structured_tuple_size = ~size_t(0); | |
template <class Tupl, class... Any> | |
constexpr size_t _struct_bases() noexcept { | |
if constexpr (requires { Tupl{_any_base<Tupl>(), Any()...}; }) | |
return _struct_bases<Tupl, Any..., _any_base<Tupl>>(); | |
return sizeof...(Any); | |
} | |
template <size_t N, class Tupl, class... Any> | |
consteval size_t _struct_size() noexcept { | |
if constexpr (N != 0) | |
if (size_t size = _struct_size<N-1, Tupl, Any..., _any>(); size != ~size_t(0)) | |
return size; | |
return requires { Tupl{Any()...}; } | |
? sizeof...(Any) - _struct_bases<Tupl>() | |
: ~size_t(0); | |
} | |
template <size_t N, class Tupl, class... Any> | |
requires (_structured_tuple_size<Tupl> != ~size_t(0)) | |
consteval size_t _struct_size() noexcept { | |
static_assert(_structured_tuple_size<Tupl> <= _max_structured_tuple_size); | |
return _structured_tuple_size<Tupl>; | |
} | |
template <class Tupl> | |
concept _has_tuple_protocol = requires { | |
{ std::tuple_size<std::remove_cvref_t<Tupl>>::value } -> std::integral; | |
}; | |
template <class Tupl> | |
concept _has_tuple_apply = requires (_declval<Tupl> tup) { | |
_apply_cpo::_fn()(_count_fn(), tup()); | |
}; | |
template <class Tupl> | |
concept _is_structured_type = | |
std::is_aggregate_v<std::remove_cvref_t<Tupl>> && | |
(_struct_size<_max_structured_tuple_size, std::remove_cvref_t<Tupl>>() != ~size_t(0)); | |
template <class Tupl> | |
concept _tuple_like = _has_tuple_protocol<Tupl> || _has_tuple_apply<Tupl> || _is_structured_type<Tupl>; | |
template <_tuple_like Tupl> | |
consteval size_t _tuple_size() { | |
if constexpr (_has_tuple_protocol<Tupl>) { | |
return std::tuple_size<std::remove_cvref_t<Tupl>>::value; | |
} | |
else if constexpr (_has_tuple_apply<Tupl>) { | |
return decltype(_apply_cpo::_fn()(_count_fn(), std::declval<Tupl>()))::value; | |
} | |
else { | |
return _struct_size<_max_structured_tuple_size, std::remove_cvref_t<Tupl>>(); | |
} | |
} | |
template <class... As, _callable_with<As...> Fn> | |
constexpr auto _struct_apply(Fn&& fn, As&&... args) noexcept -> _call_result_t<Fn, As...> { | |
return std::forward<Fn>(fn)(std::forward<As>(args)...); | |
} | |
struct _invalid_function_call { }; | |
// Below we use structured binding to unpack a struct into its members so we can pass | |
// them as arguments to a callable. We handle up to 16 members until clang implements | |
// "Structured Bindings Can Introduce a Pack". | |
#define _M0(_A) , static_cast<_copy_cvref_t<Tupl, decltype(_A)>&&>(_A) | |
#define _M1(_A) , _copy_cvref_t<Tupl, decltype(_A)> | |
#define _STRUCTURED_APPLY_FN(...) \ | |
template <class Fn, class Tupl> \ | |
constexpr decltype(auto) operator()(Fn&& fn, Tupl&& tup) const { \ | |
auto&& [__VA_ARGS__] = tup; \ | |
if constexpr (_callable_with<Fn _FOR_EACH(_M1, __VA_ARGS__)>) \ | |
return ::_struct_apply(std::forward<Fn>(fn) _FOR_EACH(_M0, __VA_ARGS__)); \ | |
else \ | |
return _invalid_function_call(); \ | |
} | |
template <size_t Size> | |
struct _structured_apply; | |
template <> | |
struct _structured_apply<0> { | |
template <class Fn> | |
constexpr decltype(auto) operator()(Fn&& fn, _ignore) const { | |
if constexpr (_callable_with<Fn>) | |
return std::forward<Fn>(fn)(); | |
else | |
return _invalid_function_call(); | |
} | |
}; | |
template <> struct _structured_apply<1> { _STRUCTURED_APPLY_FN(_0) }; | |
template <> struct _structured_apply<2> { _STRUCTURED_APPLY_FN(_0,_1) }; | |
template <> struct _structured_apply<3> { _STRUCTURED_APPLY_FN(_0,_1,_2) }; | |
template <> struct _structured_apply<4> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3) }; | |
template <> struct _structured_apply<5> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4) }; | |
template <> struct _structured_apply<6> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5) }; | |
template <> struct _structured_apply<7> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6) }; | |
template <> struct _structured_apply<8> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7) }; | |
template <> struct _structured_apply<9> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8) }; | |
template <> struct _structured_apply<10> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9) }; | |
template <> struct _structured_apply<11> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) }; | |
template <> struct _structured_apply<12> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11) }; | |
template <> struct _structured_apply<13> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12) }; | |
template <> struct _structured_apply<14> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13) }; | |
template <> struct _structured_apply<15> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14) }; | |
template <> struct _structured_apply<16> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15) }; | |
#undef _M0 | |
#undef _M1 | |
#undef _STRUCTURED_APPLY_FN | |
template <class Fn, class Tupl> | |
using _structured_apply_result_t = _call_result_t<_structured_apply<_tuple_size<Tupl>()>, Fn, Tupl>; | |
template <size_t N> | |
struct _get_nth { | |
template <class... Ts> | |
constexpr decltype(auto) operator()(Ts&&... ts) const noexcept { | |
return static_cast<Ts...[N]&&>(ts...[N]); | |
} | |
}; | |
inline constexpr struct _apply_t : _apply_cpo::_fn { | |
template <class Fn> | |
struct _nothrow_fn { | |
template <class... Ts> | |
constexpr auto operator()(Ts&&...) const noexcept { | |
return std::bool_constant<_nothrow_callable_with<Fn, Ts...>>(); | |
} | |
}; | |
using _apply_cpo::_fn::operator(); | |
template <class Fn, _is_structured_type Tupl> | |
requires (!_callable_with<_apply_cpo::_fn, Fn, Tupl>) && | |
(!std::same_as<_structured_apply_result_t<Fn, Tupl>, _invalid_function_call>) | |
constexpr auto operator()(Fn&& fn, Tupl&& tup) const | |
noexcept(_structured_apply_result_t<_nothrow_fn<Fn>, Tupl>::value) { | |
return _structured_apply<_tuple_size<Tupl>()>()(std::forward<Fn>(fn), std::forward<Tupl>(tup)); | |
} | |
} _apply {}; | |
template <class Fn, class Tup> | |
concept _applicable_with = _callable_with<_apply_t, Fn, Tup>; | |
template <class Fn, class Tup> | |
concept _nothrow_applicable_with = _nothrow_callable_with<_apply_t, Fn, Tup>; | |
template <class Fn, class Tup> | |
using _apply_result_t = _call_result_t<_apply_t, Fn, Tup>; | |
template <size_t I> | |
struct _get_t { | |
template <_tuple_like Tupl> | |
requires (_tuple_size<Tupl>() > I) | |
constexpr decltype(auto) operator()(Tupl&& tup) const noexcept { | |
if constexpr (requires { std::forward<Tupl>(tup).template get<I>(); }) { | |
return std::forward<Tupl>(tup).template get<I>(); | |
} | |
else if constexpr (requires { get<I>(std::forward<Tupl>(tup)); }) { | |
return get<I>(std::forward<Tupl>(tup)); | |
} | |
else if constexpr (requires { _apply_cpo::_fn()(_get_nth<I>(), std::forward<Tupl>(tup)); }) { | |
return _apply_cpo::_fn()(_get_nth<I>(), std::forward<Tupl>(tup)); | |
} | |
else { | |
return _structured_apply<_tuple_size<Tupl>()>()(_get_nth<I>(), std::forward<Tupl>(tup)); | |
} | |
} | |
}; | |
template <size_t I> | |
inline constexpr _get_t<I> _get {}; | |
// For creating overload sets from a set of lambdas | |
template <class... Fns> | |
struct _overload_set : Fns... { | |
using Fns::operator()...; | |
}; | |
// Awaitable helpers: | |
template <class T> | |
concept _await_suspend_result = | |
std::same_as<T, void> || std::same_as<T, bool> || _is_specialization_of<T, std::coroutine_handle>; | |
template <class A, class... Promise> | |
concept _is_awaiter = | |
requires (A& a, std::coroutine_handle<Promise...> h) { | |
a.await_ready() ? 1 : 0; | |
{ a.await_suspend(h) } -> _await_suspend_result; | |
a.await_resume(); | |
}; | |
template <class Awaitable> | |
constexpr auto _get_awaiter(Awaitable&& _awaitable, _ignore = {}) -> decltype(auto) { | |
if constexpr (requires { std::forward<Awaitable>(_awaitable).operator co_await(); }) { | |
return std::forward<Awaitable>(_awaitable).operator co_await(); | |
} else if constexpr (requires { operator co_await(std::forward<Awaitable>(_awaitable)); }) { | |
return operator co_await(std::forward<Awaitable>(_awaitable)); | |
} else { | |
return std::forward<Awaitable>(_awaitable); | |
} | |
} | |
template <class Awaitable, class Promise> | |
constexpr auto _get_awaiter(Awaitable&& _awaitable, Promise& _promise) -> decltype(auto) | |
requires requires { _promise.await_transform(std::forward<Awaitable>(_awaitable)); } { | |
if constexpr (requires { _promise.await_transform(std::forward<Awaitable>(_awaitable)).operator co_await(); }) { | |
return _promise.await_transform(std::forward<Awaitable>(_awaitable)).operator co_await(); | |
} else if constexpr (requires { operator co_await(_promise.await_transform(std::forward<Awaitable>(_awaitable))); }) { | |
return operator co_await(_promise.await_transform(std::forward<Awaitable>(_awaitable))); | |
} else { | |
return _promise.await_transform(std::forward<Awaitable>(_awaitable)); | |
} | |
} | |
template <class C, class... Promise> | |
concept _is_awaitable = | |
requires (C (*fc)() noexcept, Promise&... p) { | |
{ ::_get_awaiter(fc(), p...) } -> _is_awaiter<Promise...>; | |
}; | |
template <class Awaitable, class... Promise> | |
using _awaiter_of_t = decltype(::_get_awaiter(std::declval<Awaitable>(), std::declval<Promise&>()...)); | |
template <class Awaitable, class... Promise> | |
requires _is_awaitable<Awaitable, Promise...> | |
using _await_result_t = decltype(std::declval<_awaiter_of_t<Awaitable, Promise...>&>().await_resume()); | |
// #endregion | |
template<class T, class Promise> | |
concept _has_as_awaitable = | |
requires (T&& t, Promise& p) { | |
{ std::forward<T>(t).as_awaitable(p) } -> _is_awaitable<Promise&>; | |
}; | |
template <class Derived> | |
struct _with_await_transform { | |
template <class T> | |
T&& await_transform(T&& value) noexcept { | |
return std::forward<T>(value); | |
} | |
template<_has_as_awaitable<Derived> T> | |
decltype(auto) await_transform(T&& value) | |
noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>()))) { | |
return std::forward<T>(value).as_awaitable(static_cast<Derived&>(*this)); | |
} | |
}; | |
template <class Env> | |
struct _env_promise : _with_await_transform<_env_promise<Env>> { | |
std::suspend_never get_return_object() noexcept; | |
std::suspend_never initial_suspend() noexcept; | |
std::suspend_never final_suspend() noexcept; | |
void unhandled_exception() noexcept; | |
void return_void() noexcept; | |
std::coroutine_handle<> unhandled_stopped() noexcept; | |
const Env& get_env() const noexcept; | |
}; | |
// Exception types | |
struct std_exception { | |
std_exception() = default; | |
virtual constexpr const char* what() const noexcept { | |
return "unknown exception"; | |
} | |
}; | |
template <class Derived> | |
struct compile_time_error : std_exception { | |
compile_time_error() = default; | |
constexpr const char* what() const noexcept override { | |
return typeid(Derived*).name(); | |
} | |
}; | |
template <class Data, class... What> | |
struct _sender_type_check_failure : compile_time_error<_sender_type_check_failure<Data, What...>> { | |
_sender_type_check_failure() requires std::default_initializable<Data> = default; | |
explicit constexpr _sender_type_check_failure(Data data) | |
: data_(data) | |
{ } | |
Data data_; | |
}; | |
struct _dependent_sender_error_base : std_exception { | |
constexpr char const* what() const noexcept override { | |
return what_; | |
} | |
char const* what_; | |
}; | |
template <class Sndr> | |
struct _dependent_sender_error : _dependent_sender_error_base { | |
constexpr _dependent_sender_error() noexcept { | |
what_ = "This sender needs to know its execution environment before it can know how it will complete."; | |
} | |
}; | |
// Concept tag types | |
struct sender_t { | |
using sender_concept = sender_t; | |
}; | |
struct receiver_t { | |
using receiver_concept = receiver_t; | |
}; | |
struct operation_state_t { | |
using operation_state_concept = operation_state_t; | |
}; | |
struct scheduler_t { | |
using scheduler_concept = scheduler_t; | |
}; | |
// Completion tags (set_value, set_error, set_stopped): | |
enum class _disposition { _value, _error, _stopped }; | |
template <class Rcvr, _disposition Disp, class... Ts> | |
concept _has_set_xxx = | |
bool( | |
requires(_declval<Rcvr> rcvr, _declval<Ts>... ts) { | |
requires (Disp == _disposition::_value && requires { rcvr().set_value(ts()...); }) | |
|| (Disp == _disposition::_error && sizeof...(Ts) == 1 && requires { rcvr().set_error(ts()...); }) | |
|| (Disp == _disposition::_stopped && sizeof...(Ts) == 0 && requires { rcvr().set_stopped(ts()...); }); | |
} | |
); | |
template <_disposition Disp> | |
struct _set_xxx_t { | |
template <enum _disposition Other> | |
constexpr bool operator==(_set_xxx_t<Other>) const noexcept { | |
return Disp == Other; | |
} | |
template <class... Ts, _has_set_xxx<Disp, Ts...> Rcvr> | |
constexpr void operator()(Rcvr&& rcvr, Ts&&... ts) const noexcept { | |
if constexpr (Disp == _disposition::_value) std::move(rcvr).set_value(std::forward<Ts>(ts)...); | |
if constexpr (Disp == _disposition::_error) std::move(rcvr).set_error(std::forward<Ts>(ts)...); | |
if constexpr (Disp == _disposition::_stopped) std::move(rcvr).set_stopped(std::forward<Ts>(ts)...); | |
} | |
static constexpr bool _valid_arg_count(size_t count) noexcept { | |
if constexpr (Disp == _disposition::_error) return count == 1; | |
if constexpr (Disp == _disposition::_stopped) return count == 0; | |
return true; | |
} | |
static constexpr _disposition _disposition = Disp; | |
}; | |
inline constexpr struct set_value_t : _set_xxx_t<_disposition::_value> {} set_value{}; | |
inline constexpr struct set_error_t : _set_xxx_t<_disposition::_error> {} set_error{}; | |
inline constexpr struct set_stopped_t : _set_xxx_t<_disposition::_stopped> {} set_stopped{}; | |
template <class Sig> | |
extern std::false_type _is_completion_signature_v; | |
template <class... Ts> | |
extern std::true_type _is_completion_signature_v<set_value_t(Ts...)>; | |
template <class Error> | |
extern std::true_type _is_completion_signature_v<set_error_t(Error)>; | |
template<> | |
std::true_type _is_completion_signature_v<set_stopped_t()>; | |
template <class Sig> | |
concept _completion_signature = decltype(_is_completion_signature_v<Sig>)::value; | |
template <_completion_signature... Sigs> | |
struct completion_signatures; | |
inline constexpr auto _to_completions = | |
[]<class... Sigs>() { return completion_signatures<Sigs...>(); }; | |
template <class Tag> | |
inline constexpr auto _default_transform_fn = | |
[]<class... Ts>() -> completion_signatures<Tag(Ts...)> { return {}; }; | |
template <class Tag, class... As> | |
auto _normalize2(As&&...) -> Tag(*)(As...); | |
template <class Tag, class... As> | |
extern decltype(::_normalize2<Tag>(std::declval<As>()...)) _normalize2_v; | |
template <class Tag, class... As> | |
using _normalized_args_t = decltype(auto(_normalize2_v<Tag, As...>)); | |
template <class Tag, class... As> | |
auto _normalize1(Tag(*)(As...)) -> decltype(::_normalize2<Tag>(std::declval<As>()...)); | |
template <class Sig> | |
extern decltype(::_normalize1(static_cast<Sig*>(nullptr))) _normalize1_v; | |
template <class Sig> | |
using _normalized_sig_t = decltype(auto(_normalize1_v<Sig>)); | |
template <class... Sigs> | |
constexpr auto _unique(Sigs*...) { | |
return (_typeset() + completion_signatures<Sigs...>())._apply(_to_completions); | |
} | |
template <auto Tag, class... Ts> | |
using signature = decltype(Tag)(*)(Ts...); | |
template <class... Ts> | |
inline constexpr auto value = signature<set_value, Ts...>(); | |
template <> | |
inline constexpr auto value<void> = signature<set_value>(); | |
template <class Err = std::exception_ptr> | |
inline constexpr auto error = signature<set_error, Err>(); | |
inline constexpr auto stopped = signature<set_stopped>(); | |
template <class... Sigs> | |
auto _mk_sigset(Sigs*...) -> _mk_typeset<Sigs...>; | |
// completion_signatures | |
template <_completion_signature... Sigs> | |
struct completion_signatures { | |
private: | |
struct _checked { | |
using _typeset = decltype(::_mk_sigset(_normalized_sig_t<Sigs>()...)); | |
static_assert(_typeset().size() == sizeof...(Sigs)); | |
}; | |
template <class Fn, class Tag, class... As> | |
static constexpr auto _filter1(Tag(*)(As...)) | |
-> _typeset_element<_if<_callable_with<Fn, Tag(*)(As...)>, Tag(As...), _ignore>>; | |
public: | |
completion_signatures() = default; | |
explicit constexpr completion_signatures(auto*, auto*...) noexcept { | |
} | |
static constexpr std::size_t size() noexcept { | |
return sizeof...(Sigs); | |
} | |
template <std::size_t I> | |
static constexpr auto get() noexcept { | |
return _normalized_sig_t<Sigs...[I]>(); | |
} | |
template <std::size_t I> | |
friend constexpr auto get(completion_signatures) noexcept { | |
return _normalized_sig_t<Sigs...[I]>(); | |
} | |
template <class... Other> | |
constexpr auto operator+(completion_signatures<Other...> cs) const noexcept { | |
return (typename _checked::_typeset() + cs)._apply(_to_completions); | |
} | |
template <class Tag, class... As> | |
constexpr friend auto operator+(completion_signatures, Tag(*)(As...)) noexcept { | |
using Sig = std::remove_pointer_t<_normalized_args_t<Tag, As...>>; | |
return (typename _checked::_typeset() + _typeset_element<Sig>())._apply(_to_completions); | |
} | |
template <class Tag, class... As> | |
constexpr friend auto operator+(Tag(*sig)(As...), completion_signatures sigs) noexcept { | |
return sigs + sig; | |
} | |
template <_callable_with<Sigs*...> Fn> | |
static constexpr decltype(auto) apply(Fn fn) noexcept(_nothrow_callable_with<Fn, Sigs*...>) { | |
return fn(_normalized_sig_t<Sigs>()...); | |
} | |
template <class Fn> | |
static constexpr auto filter(Fn) { | |
return (_typeset() +...+ decltype(_filter1<Fn>(_normalized_sig_t<Sigs>()))())._apply(_to_completions); | |
} | |
template <class Tag> | |
static constexpr auto select(Tag) noexcept { | |
return filter([]<class... Ts>(Tag(*)(Ts...)) { }); | |
} | |
template <class MapFn, class ReduceFn = decltype([](auto... ts) { return (ts +...); })> | |
static constexpr auto transform(MapFn map, ReduceFn reduce = {}) { | |
return reduce(map(_normalized_sig_t<Sigs>())...); | |
} | |
template <class... OtherSigs> | |
static constexpr bool contains(completion_signatures<OtherSigs...>) noexcept { | |
using Self = typename _checked::_typeset; | |
return (__is_base_of(_typeset_element<_normalized_sig_t<OtherSigs>>, Self) &&...); | |
} | |
template <class... OtherSigs> | |
constexpr bool operator==(completion_signatures<OtherSigs...> other) const noexcept { | |
if constexpr (sizeof...(Sigs) == sizeof...(OtherSigs)) | |
return contains(other); | |
else | |
return false; | |
} | |
}; | |
template <class... Sigs> | |
completion_signatures(Sigs*...) -> completion_signatures<std::remove_pointer_t<_normalized_sig_t<Sigs>>...>; | |
template <class... Sigs> | |
struct std::tuple_size<completion_signatures<Sigs...>> | |
: _size_t<sizeof...(Sigs)> | |
{}; | |
template <size_t I, class... Sigs> | |
struct std::tuple_element<I, completion_signatures<Sigs...>> { | |
using type = Sigs...[I]*; | |
}; | |
//! @brief builds a completion_signatures specialization by normalizing | |
//! all the signatures and then removing duplicates. To normalize a signature | |
//! is to remove rvalue references from arguments. For example, | |
//! set_value_t(int&&, float&) normalizes to set_value_t(int, float&). | |
template <class... Sigs> | |
inline constexpr auto _completions_v = ::_unique(_normalized_sig_t<Sigs>()...); | |
template <bool PotentiallyThrowing> | |
inline constexpr auto eptr_completion_if = | |
_if<PotentiallyThrowing, | |
completion_signatures<set_error_t(std::exception_ptr)>, | |
completion_signatures<>>(); | |
template <class T> | |
concept _valid_completion_signatures = _is_specialization_of<T, completion_signatures>; | |
template <class... Sigs1, class... Sigs2> | |
constexpr auto make_completion_signatures(Sigs2*... sigs2) noexcept { | |
return _completions_v<Sigs1..., Sigs2...>; | |
} | |
template <class... What, class... Values> | |
[[noreturn, nodiscard]] | |
consteval completion_signatures<> invalid_completion_signature(Values... values) { | |
if constexpr (sizeof...(Values) == 1) { | |
throw _sender_type_check_failure<Values...[0], What...>(values...); | |
} | |
else { | |
(void) ::invalid_completion_signature<What...>([...data = values]{}); | |
} | |
} | |
template <class... As, _meta_callable_with<As...> Fn> | |
constexpr auto _transform_expr(const Fn& fn) requires (sizeof...(As) != 0) { | |
return fn.template operator()<As...>(); | |
} | |
template <_callable_with Fn> | |
constexpr auto _transform_expr(const Fn& fn) { | |
return fn(); | |
} | |
// transform_completion_signatures: | |
template <class... As, class Fn> | |
consteval auto _apply_transform(const Fn& fn) { | |
if constexpr (requires { { ::_transform_expr<As...>(fn) } -> _valid_completion_signatures; }) { | |
return ::_transform_expr<As...>(fn); | |
} | |
else { | |
if constexpr (requires { ::_transform_expr<As...>(fn); }) { | |
(void) ::_transform_expr<As...>(fn); // potentially throwing | |
} | |
return ::invalid_completion_signature< | |
struct IN_TRANSFORM_COMPLETION_SIGNATURES, | |
struct A_TRANSFORM_FUNCTION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION, | |
struct WITH_FUNCTION(Fn), | |
struct WITH_ARGUMENTS(As...)>(); | |
} | |
} | |
template <_valid_completion_signatures Completions, | |
class ValueFn = decltype(_default_transform_fn<set_value_t>), | |
class ErrorFn = decltype(_default_transform_fn<set_error_t>), | |
class StoppedFn = decltype(_default_transform_fn<set_stopped_t>), | |
_valid_completion_signatures ExtraSigs = completion_signatures<>> | |
constexpr auto transform_completion_signatures(Completions cs, | |
ValueFn value_fn = {}, | |
ErrorFn error_fn = {}, | |
StoppedFn stopped_fn = {}, | |
ExtraSigs extra = {}) { | |
auto transform1 = [=]<class Tag, class... Ts>(Tag(*)(Ts...)) { | |
if constexpr (Tag() == set_value) | |
return _apply_transform<Ts...>(value_fn); | |
else if constexpr (Tag() == set_error) | |
return _apply_transform<Ts...>(error_fn); | |
else | |
return _apply_transform<Ts...>(stopped_fn); | |
}; | |
auto transform_all = [=](auto*... sigs) { | |
return (transform1(sigs) +...+ completion_signatures()); | |
}; | |
return _apply(transform_all, cs) + extra; | |
} | |
// clang crashes if this is a lambda variable template instead of a class template | |
template <_valid_completion_signatures Completions, | |
template <class...> class Tuple, | |
template <class...> class Variant> | |
using _value_types = _constant_wrapper< | |
Completions().select(set_value).transform( | |
[]<class...Ts>(set_value_t(*)(Ts...)) { return _typelist<Ts...>{}; }, | |
[]<class...Ts>(Ts...) { return std::type_identity<Variant<typename Ts::template _apply<Tuple>...>>{}; }) | |
>::type::type; | |
template <_valid_completion_signatures Completions, | |
template <class...> class Variant> | |
using _error_types = _constant_wrapper< | |
Completions().select(set_error).transform( | |
[]<class Error>(set_error_t(*)(Error)) { return std::type_identity<Error>{}; }, | |
[]<class...Ts>(Ts...) { return std::type_identity<Variant<typename Ts::type...>>{}; }) | |
>::type::type; | |
// Environments | |
template <class Env> | |
concept queryable = std::destructible<Env>; | |
template <class Env, class Query> | |
using _query_result_t = decltype(std::declval<Env>().query(std::declval<Query>())); | |
template <class Env, class Query> | |
concept _has_query = _can_be_instantiated_with<_query_result_t, Env, Query>; | |
template <class Query, queryable Env, class Default> | |
constexpr decltype(auto) _query_or_default(Query q, Env&& env, Default def) { | |
if constexpr (_callable_with<Query, Env>) { | |
return q(std::forward<Env>(env)); | |
} | |
else { | |
return auto(std::move(def)); | |
} | |
} | |
template <class Query, class Value> | |
struct prop : Query { | |
constexpr const Value& query(Query) const noexcept { | |
return value; | |
} | |
Value value; | |
}; | |
template <queryable... Envs> | |
struct env : _tuple<Envs...> { | |
template <class Query> | |
static constexpr size_t _first_index() noexcept { | |
bool _map[]{_has_query<Envs, Query>...}; | |
return std::ranges::find(_map, true) - _map; | |
} | |
template <class Query> | |
requires (_has_query<Envs, Query> ||...) | |
constexpr decltype(auto) query(Query q) const noexcept { | |
return this->template get<_first_index<Query>()>().query(q); | |
} | |
}; | |
template <class... Envs> | |
env(Envs...) -> env<std::unwrap_reference_t<Envs>...>; | |
// Forwarding queries | |
inline constexpr struct forwarding_query_t { | |
template <class Query> | |
constexpr bool operator()(Query q) const noexcept { | |
if constexpr (q.query(*this)) { | |
static_assert(requires { {q.query(*this)} noexcept -> std::same_as<bool>; }); | |
} | |
else { | |
return std::derived_from<Query, forwarding_query_t>; | |
} | |
} | |
} forwarding_query {}; | |
template <class Query> | |
concept _forwarding_query = forwarding_query(Query()); | |
template <class Env> | |
struct _fwd_env { | |
Env env_; | |
template <_forwarding_query Query> | |
requires _has_query<Env, Query> | |
constexpr decltype(auto) operator()(Query q) const noexcept(noexcept(env_.query(q))) { | |
return env_.query(q); | |
} | |
}; | |
template <class Env> | |
constexpr _fwd_env<Env> _FWD_ENV(Env&& env) noexcept(noexcept(_fwd_env<Env>(std::forward<Env>(env)))) { | |
return _fwd_env<Env>(std::forward<Env>(env)); | |
} | |
template <class Env> | |
requires _is_specialization_of<std::remove_cvref_t<Env>, _fwd_env> | |
constexpr Env _FWD_ENV(Env&& env) noexcept(noexcept(Env(std::forward<Env>(env)))) { | |
return std::forward<Env>(env); | |
} | |
template <class Env> | |
using _FWD_ENV_T = decltype(_FWD_ENV(std::declval<Env>())); | |
// get_env and env_of_t | |
inline constexpr struct get_env_t { | |
template <class EP> | |
constexpr decltype(auto) operator()(EP&& ep) const noexcept { | |
if constexpr (requires { ep.get_env(); }) { | |
static_assert(requires { { ep.get_env() } noexcept -> queryable; }); | |
return ep.get_env(); | |
} | |
else { | |
return env(); | |
} | |
} | |
} get_env {}; | |
template <class EnvProvider> | |
using env_of_t = decltype(get_env(std::declval<EnvProvider>())); | |
// concept: sender | |
template <class Sndr> | |
inline constexpr bool enable_sender = false; | |
template<class Sndr> | |
concept _is_sender = | |
std::derived_from<typename Sndr::sender_concept, sender_t> || | |
enable_sender<Sndr>; | |
template<class Sndr> | |
concept _sender_or_awaitable = | |
_is_sender<Sndr> || | |
_is_awaitable<Sndr, _env_promise<env<>>>; | |
template<class Sndr> | |
concept sender = | |
bool(_sender_or_awaitable<std::remove_cvref_t<Sndr>>) && | |
requires (const std::remove_cvref_t<Sndr>& sndr) { | |
{ get_env(sndr) } -> queryable; | |
} && | |
std::move_constructible<std::remove_cvref_t<Sndr>> && | |
std::constructible_from<std::remove_cvref_t<Sndr>, Sndr>; | |
// basic_sender | |
struct _transformable_sender_fn { | |
void operator()(auto, auto&&, sender auto&&...) const noexcept {} | |
}; | |
// Non-standard | |
template <class Sndr> | |
concept _transformable_sender = | |
sender<Sndr> && | |
_tuple_like<Sndr> && | |
requires (_declval<Sndr> sndr) { | |
((decltype(_apply(_transformable_sender_fn(), sndr()))*) nullptr); | |
}; | |
// tag_of_t: for getting the algorithm tag type of a transformable sender | |
template <_transformable_sender Sndr> | |
using tag_of_t = decltype(auto(_get<0>(std::declval<Sndr>()))); | |
// domains | |
struct default_domain { | |
template <class Sndr, class... Env> | |
static constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) { | |
if constexpr (requires { tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...); }) { | |
return tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...); | |
} | |
else { | |
return static_cast<Sndr>(std::forward<Sndr>(sndr)); | |
} | |
} | |
}; | |
inline constexpr struct get_domain_t : forwarding_query_t { | |
template <_has_query<get_domain_t> Env> | |
constexpr decltype(auto) operator()(const Env& env) const noexcept { | |
static_assert(noexcept(env.query(*this))); | |
return env.query(*this); | |
} | |
} get_domain {}; | |
template<class Sndr> | |
constexpr auto _get_domain_early(const Sndr& sndr) noexcept { | |
if constexpr (requires { get_domain(get_env(sndr)); }) { | |
return decltype(get_domain(get_env(sndr)))(); | |
} | |
// else if constexpr (requires { _completion_domain(sndr); }) { | |
// return _completion_domain(sndr); | |
// } | |
else { | |
return default_domain(); | |
} | |
} | |
template <class Sndr> | |
using _early_domain_of_t = decltype(::_get_domain_early(std::declval<Sndr>())); | |
template<class Sndr, class Env> | |
constexpr auto _get_domain_late(const Sndr& sndr, const Env& env) noexcept { | |
// if constexpr (_sender_for<Sndr, continues_on_t>) { | |
// auto& [_, sched, _] = sndr; | |
// return _query_or_default(get_domain, sched, default_domain()); | |
// } else | |
if constexpr (requires { get_domain(get_env(sndr)); }) { | |
return decltype(get_domain(get_env(sndr))){}; | |
} | |
// else if constexpr (requires { _completion_domain(sndr); }) { | |
// return _completion_domain(sndr); | |
// } | |
else if constexpr (requires { get_domain(env); }) { | |
return decltype(get_domain(env)){}; | |
} | |
else if constexpr (requires { get_domain(get_scheduler(env)); }) { | |
return decltype(get_domain(get_scheduler(env))){}; | |
} | |
else { | |
return default_domain(); | |
} | |
} | |
template <class Sndr, class Env> | |
using _late_domain_of_t = decltype(::_get_domain_late(std::declval<Sndr>(), std::declval<Env>())); | |
// transform_sender | |
struct _transform_sender_fn { | |
template<class Self = _transform_sender_fn, class Domain, sender Sndr, queryable... Env> | |
constexpr decltype(auto) operator()(Domain dom, Sndr&& sndr, const Env&... env) const { //noexcept(see below); | |
constexpr bool has_transform = requires { dom.transform_sender(std::forward<Sndr>(sndr), env...); }; | |
using NewDomain = _if<has_transform, Domain, default_domain>; | |
using NewSndr = decltype(NewDomain().transform_sender(std::forward<Sndr>(sndr), env...)); | |
if constexpr (std::same_as<std::remove_cvref_t<Sndr>, std::remove_cvref_t<NewSndr>>) { | |
return NewDomain().transform_sender(std::forward<Sndr>(sndr), env...); | |
} | |
else { | |
return Self()(dom, NewDomain().transform_sender(std::forward<Sndr>(sndr), env...), env...); | |
} | |
} | |
}; | |
template<class Domain, sender Sndr, queryable... Env> | |
constexpr sender decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) { //noexcept(see below); | |
return _transform_sender_fn()(dom, std::forward<Sndr>(sndr), env...); | |
} | |
template <class Domain, class Sndr, class... Env> | |
using _transform_sender_result_t = decltype(::transform_sender(Domain(), std::declval<Sndr>(), std::declval<Env>()...)); | |
// get_completion_signatures | |
#define _GET_COMPLSIGS(Sndr, ...) \ | |
std::remove_reference_t<Sndr>::template get_completion_signatures<Sndr __VA_OPT__(,) __VA_ARGS__>() | |
#define _CHECKED_COMPLSIGS(...) (__VA_ARGS__, ::_checked_complsigs<decltype(__VA_ARGS__)>()) | |
template <class Sndr> | |
using _nested_complsigs_t = std::remove_reference_t<Sndr>::completion_signatures; | |
template <class Completions> | |
consteval auto _checked_complsigs() { | |
if constexpr (_valid_completion_signatures<Completions>) | |
return Completions(); | |
else | |
return invalid_completion_signature</*TODO*/>(); | |
} | |
template <class Sndr, class... Env> | |
consteval auto _get_completion_signatures_helper() { | |
if constexpr (requires { _GET_COMPLSIGS(Sndr, Env...); }) { | |
return _CHECKED_COMPLSIGS(_GET_COMPLSIGS(Sndr, Env...)); | |
} | |
else if constexpr (requires { _GET_COMPLSIGS(Sndr); }) { | |
return _CHECKED_COMPLSIGS(_GET_COMPLSIGS(Sndr)); | |
} | |
else if constexpr (requires { _nested_complsigs_t<Sndr>(); }) { | |
return _CHECKED_COMPLSIGS(_nested_complsigs_t<Sndr>()); | |
} | |
else if constexpr (_is_awaitable<Sndr, _env_promise<Env>...>) { | |
using Result = _await_result_t<Sndr, _env_promise<Env>...>; | |
return completion_signatures{value<Result>, | |
error<>, | |
stopped}; | |
} | |
else if constexpr (sizeof...(Env) == 0) { | |
return (throw _dependent_sender_error<Sndr>(), _completions_v<>); | |
} | |
else { | |
return invalid_completion_signature</*TODO*/>(); | |
} | |
} | |
template <class Sndr> | |
consteval auto get_completion_signatures() -> _valid_completion_signatures auto { | |
return _get_completion_signatures_helper<Sndr>(); | |
} | |
template <class Sndr, class Env> | |
consteval auto get_completion_signatures() -> _valid_completion_signatures auto { | |
// Apply a lazy sender transform if one exists before computing the completion signatures: | |
using NewSndr = _transform_sender_result_t<_late_domain_of_t<Sndr, Env>, Sndr, Env>; | |
return _get_completion_signatures_helper<NewSndr, Env>(); | |
} | |
template <class Parent, class Child, class... Env> | |
consteval auto get_child_completion_signatures() { | |
return get_completion_signatures<_copy_cvref_t<Parent, Child>, _FWD_ENV_T<Env>...>(); | |
} | |
#undef _GET_COMPLSIGS | |
#undef _CHECKED_COMPLSIGS | |
// Receiver concepts | |
template <class Rcvr> | |
concept receiver = | |
std::derived_from<typename std::remove_reference_t<Rcvr>::receiver_concept, receiver_t> && | |
requires (Rcvr& rcvr) { | |
get_env(rcvr); | |
} && | |
std::move_constructible<Rcvr>; | |
template <class Rcvr, class Signature> | |
struct _missing_completion_signature | |
: compile_time_error<_missing_completion_signature<Rcvr, Signature>> | |
{}; | |
template <class Rcvr> | |
constexpr auto _has_completion_helper = | |
[]<class Tag, class... As>(Tag(*)(As...)) { | |
return _callable_with<Tag, Rcvr, As...> | |
? true | |
: (throw _missing_completion_signature<Rcvr, Tag(As...)>(), false); | |
}; | |
template <class Rcvr> | |
constexpr auto _has_completions = | |
[](auto... sigs) { | |
return (_has_completion_helper<Rcvr>(sigs) &&...); | |
}; | |
template <class Rcvr, class Completions> | |
concept receiver_of = | |
receiver<Rcvr> && | |
std::bool_constant<_apply(_has_completions<Rcvr>, Completions())>::value; | |
// start | |
extern const struct start_t start; | |
inline constexpr struct start_t { | |
template <class Op> | |
constexpr auto operator()(Op& op) const noexcept requires requires { op.start(); } { | |
static_assert(noexcept(op.start())); | |
op.start(); | |
} | |
} start {}; | |
// Operation state concept | |
template <class Op> | |
concept operation_state = | |
std::derived_from<typename Op::operation_state_concept, operation_state_t> && | |
requires (Op& op) { | |
start(op); | |
}; | |
// Sender concepts (incomplete) | |
template <class Sndr, class... Env> | |
concept sender_in = | |
sender<Sndr> && | |
(queryable<Env> &&...) && | |
_is_constant<get_completion_signatures<Sndr, Env...>()>; | |
template <class Sndr, class... Env> | |
requires sender_in<Sndr, Env...> | |
using completion_signatures_of_t = decltype(get_completion_signatures<Sndr, Env...>()); | |
template <class Sndr> | |
consteval bool _is_dependent_sender_helper() try { | |
(void) get_completion_signatures<Sndr>(); | |
return false; | |
} catch (_dependent_sender_error_base&) { | |
return true; | |
} | |
template <class Sndr> | |
concept dependent_sender = | |
sender<Sndr> && | |
std::bool_constant<_is_dependent_sender_helper<Sndr>()>::value; | |
// _single_sender: [execution.syn] p2-3 | |
auto _single_sender_value_helper(_typelist<>) -> void; | |
auto _single_sender_value_helper(_typelist<_typelist<>>) -> void; | |
template <class T> | |
auto _single_sender_value_helper(_typelist<_typelist<T>>) -> T; | |
template <class... Ts> | |
auto _single_sender_value_helper(_typelist<_typelist<Ts...>>) -> std::tuple<Ts...>; | |
template <class Sndr, class... Env> | |
requires sender_in<Sndr, Env...> | |
using _single_sender_value_t = | |
decltype(::_single_sender_value_helper(_value_types<completion_signatures_of_t<Sndr, Env...>, _typelist, _typelist>())); | |
template <class Sndr, class... Env> | |
concept _single_sender = requires { typename _single_sender_value_t<Sndr, Env...>; }; | |
// connect awaitables | |
template <class Rcvr> | |
struct _connect_awaitable_promise; | |
template <class Sndr, class Rcvr> | |
using _connect_awaitable_result_t = _await_result_t<Sndr, _connect_awaitable_promise<Rcvr>>; | |
template <class Sndr, class Rcvr> | |
using _connect_awaitable_completions_t = | |
decltype(completion_signatures{value<_connect_awaitable_result_t<Sndr, Rcvr>>, | |
error<>, | |
stopped}); | |
struct _operation_state_task_base { | |
using operation_state_concept = ::operation_state_t; | |
explicit _operation_state_task_base(std::coroutine_handle<> h) noexcept | |
: coro_(h) | |
{} | |
_operation_state_task_base(_operation_state_task_base&&) = delete; | |
~_operation_state_task_base() { | |
coro_.destroy(); | |
} | |
void start() & noexcept { | |
coro_.resume(); | |
} | |
private: | |
std::coroutine_handle<> coro_; | |
}; | |
template <class Rcvr> | |
struct _operation_state_task : _operation_state_task_base { | |
using _operation_state_task_base::_operation_state_task_base; | |
using promise_type = _connect_awaitable_promise<Rcvr>; | |
}; | |
struct _connect_awaitable_promise_base { | |
std::suspend_always initial_suspend() noexcept { | |
return {}; | |
} | |
[[noreturn]] std::suspend_always final_suspend() noexcept { | |
std::terminate(); | |
} | |
[[noreturn]] void unhandled_exception() noexcept { | |
std::terminate(); | |
} | |
[[noreturn]] void return_void() noexcept { | |
std::terminate(); | |
} | |
}; | |
template <class Rcvr> | |
struct _connect_awaitable_promise | |
: _with_await_transform<_connect_awaitable_promise<Rcvr>> | |
, _connect_awaitable_promise_base { | |
_connect_awaitable_promise(_ignore, Rcvr& rcvr) noexcept | |
: rcvr_(rcvr) | |
{} | |
std::coroutine_handle<> unhandled_stopped() noexcept { | |
set_stopped(std::move(rcvr_)); | |
return std::noop_coroutine(); | |
} | |
_operation_state_task<Rcvr> get_return_object() noexcept { | |
return _operation_state_task<Rcvr>{ | |
std::coroutine_handle<_connect_awaitable_promise>::from_promise(*this)}; | |
} | |
env_of_t<Rcvr> get_env() const noexcept { | |
return ::get_env(rcvr_); | |
} | |
private: | |
Rcvr& rcvr_; | |
}; | |
struct _suspend_complete_awaiter_base { | |
static constexpr bool await_ready() noexcept { | |
return false; | |
} | |
[[noreturn]] void await_resume() noexcept { | |
std::unreachable(); | |
} | |
}; | |
template <class Fun> | |
struct _suspend_complete_awaiter : _suspend_complete_awaiter_base { | |
Fun fn; | |
void await_suspend(std::coroutine_handle<>) noexcept { | |
fn(); | |
} | |
}; | |
template <class Fun, class... Ts> | |
auto _suspend_complete(Fun fun, Ts&&... as) noexcept { | |
return _suspend_complete_awaiter{{}, [&, fun]() noexcept { fun(std::forward<Ts>(as)...); }}; | |
} | |
template <class Sndr, class Rcvr> | |
requires receiver_of<Rcvr, _connect_awaitable_completions_t<Sndr, Rcvr>> | |
_operation_state_task<Rcvr> _connect_awaitable(Sndr sndr, Rcvr rcvr) { | |
std::exception_ptr ep; | |
try { | |
if constexpr (std::same_as<void, _connect_awaitable_result_t<Sndr, Rcvr>>) { | |
co_await std::move(sndr); | |
co_await ::_suspend_complete(set_value, std::move(rcvr)); | |
} else { | |
co_await ::_suspend_complete(set_value, std::move(rcvr), co_await std::move(sndr)); | |
} | |
} catch(...) { | |
ep = std::current_exception(); | |
} | |
co_await _suspend_complete(set_error, std::move(rcvr), std::move(ep)); | |
} | |
// connect | |
template <class Sndr, class Rcvr> | |
concept _has_connect = | |
requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { | |
transform_sender(domain, sndr(), get_env(rcvr())).connect(rcvr()); | |
}; | |
template <class Sndr, class Rcvr> | |
concept _has_nothrow_connect = | |
requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { | |
{ transform_sender(domain, sndr(), get_env(rcvr())).connect(rcvr()) } noexcept; | |
}; | |
template <class Sndr, class Rcvr> | |
concept _has_connect_awaitable = | |
requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { | |
_connect_awaitable(transform_sender(domain, sndr(), get_env(rcvr())), rcvr()); | |
}; | |
inline constexpr struct connect_t { | |
template <class Sndr, class Rcvr> | |
requires _has_connect<Sndr, Rcvr> || _has_connect_awaitable<Sndr, Rcvr> | |
constexpr auto operator()(Sndr&& sndr, Rcvr&& rcvr) const noexcept(_has_nothrow_connect<Sndr, Rcvr>) { | |
static_assert(sender_in<Sndr, env_of_t<Rcvr>>); | |
static_assert(receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>>); | |
using Domain = _late_domain_of_t<Sndr, env_of_t<Rcvr>>; | |
using NewSndr = _transform_sender_result_t<Domain, Sndr, env_of_t<Rcvr>>; | |
const auto& env = get_env(rcvr); | |
if constexpr (_has_connect<Sndr, Rcvr>) { | |
return transform_sender(Domain(), std::forward<Sndr>(sndr), env).connect(std::forward<Rcvr>(rcvr)); | |
} | |
else { | |
return ::_connect_awaitable(transform_sender(Domain(), std::forward<Sndr>(sndr), env), std::forward<Rcvr>(rcvr)); | |
} | |
} | |
} connect {}; | |
template <class Sndr, class Rcvr> | |
concept sender_to = | |
sender_in<Sndr, env_of_t<Rcvr>> && | |
receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> && | |
requires (Sndr&& sndr, Rcvr&& rcvr) { | |
connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr)); | |
}; | |
template <class Sndr, class Rcvr> | |
requires sender_to<Sndr, Rcvr> | |
using connect_result_t = decltype(connect(std::declval<Sndr>(), std::declval<Rcvr>())); | |
//! _not_a_sender is a type to be returned from `transform_sender` when the | |
//! the given sender and environment do not type-check. | |
//! | |
//! @tparam ThrowFn is a nullary callable that should cause a meaningful exception | |
//! to be thrown. | |
template <auto ThrowFn> | |
struct _not_a_sender { | |
static_assert(_callable_with<decltype(ThrowFn)>); | |
using sender_concept = sender_t; | |
template <class Self> | |
static constexpr auto get_completion_signatures() { | |
ThrowFn(); // This _should_ exit with a meaningful exception | |
return invalid_completion_signature<struct NOT_A_SENDER>(); | |
} | |
}; | |
template <class Scheduler> | |
concept _has_schedule = | |
requires (_declval<Scheduler> sched) { | |
sched().schedule(); | |
}; | |
template <class Scheduler> | |
concept _has_nothrow_schedule = | |
requires (_declval<Scheduler> sched) { | |
{sched().schedule()} noexcept; | |
}; | |
// schedule | |
inline constexpr struct schedule_t { | |
template <_has_schedule Scheduler> | |
constexpr decltype(auto) operator()(Scheduler&& sched) const noexcept(_has_nothrow_schedule<Scheduler>) { | |
using Sndr = decltype(std::forward<Scheduler>(sched).schedule()); | |
static_assert(sender<Sndr>); | |
return std::forward<Scheduler>(sched).schedule(); | |
} | |
} schedule {}; | |
template <class Scheduler> | |
using schedule_result_t = decltype(schedule(std::declval<Scheduler>())); | |
// Scheduler concept (incomplete) | |
template <class Sched> | |
concept scheduler = | |
std::derived_from<typename std::remove_reference_t<Sched>::scheduler_concept, scheduler_t> && | |
queryable<Sched> && | |
requires (_declval<Sched> sched) { | |
{ schedule(sched()) } -> sender; | |
} && | |
std::equality_comparable<Sched> && | |
std::copyable<Sched>; | |
// Stop tokens | |
struct never_stop_token { | |
private: | |
struct _callback_type { // exposition only | |
explicit _callback_type(never_stop_token, auto&&) noexcept {} | |
}; | |
public: | |
template<class> | |
using callback_type = _callback_type; | |
static constexpr bool stop_requested() noexcept { return false; } | |
static constexpr bool stop_possible() noexcept { return false; } | |
bool operator==(const never_stop_token&) const = default; | |
}; | |
template <class Token> | |
concept stoppable_token = true; // TODO | |
// Queries | |
inline constexpr struct get_stop_token_t : forwarding_query_t { | |
template <class Env> | |
constexpr decltype(auto) operator()(const Env& env) const noexcept { | |
if constexpr (_has_query<Env, get_stop_token_t>) { | |
static_assert(requires { { env.query(*this) } noexcept -> stoppable_token; }); | |
return env.query(*this); | |
} | |
else { | |
return never_stop_token(); | |
} | |
} | |
} get_stop_token {}; | |
inline constexpr struct get_scheduler_t : forwarding_query_t { | |
template <_has_query<get_scheduler_t> Env> | |
constexpr decltype(auto) operator()(const Env& env) const noexcept { | |
static_assert(requires { { env.query(*this) } noexcept -> scheduler; }); | |
return env.query(*this); | |
} | |
} get_scheduler {}; | |
// basic-sender | |
template <class Sndr> | |
using _data_type = decltype(_get<1>(std::declval<Sndr>())); // non-standard | |
template<class Sndr, size_t I = 0> | |
using _child_type = decltype(_get<I+2>(std::declval<Sndr>())); | |
template<class Sndr> | |
using _indices_for = std::make_index_sequence<std::remove_reference_t<Sndr>::_children::size>; | |
template <class Sndr, class Tag> | |
concept _sender_for = | |
_transformable_sender<Sndr> && | |
std::same_as<tag_of_t<Sndr>, Tag>; | |
struct _default_impls { | |
static constexpr auto _get_attrs = | |
[](const auto&, const auto&... child) noexcept -> decltype(auto) { | |
if constexpr (sizeof...(child) == 1) | |
return (_FWD_ENV(get_env(child)), ...); | |
else | |
return env(); | |
}; | |
static constexpr auto _get_env = | |
[](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) { | |
return _FWD_ENV(get_env(rcvr)); | |
}; | |
static constexpr auto _get_state = | |
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) { | |
return std::forward<Sndr>(sndr).data; | |
}; | |
static constexpr auto _start = | |
[](auto&, auto&, auto&... ops) noexcept -> void { | |
(start(ops), ...); | |
}; | |
static constexpr auto _complete = | |
[]<class Index, class Rcvr, class Tag, class... As>( | |
Index, auto& state, Rcvr& rcvr, Tag, As&&... args) noexcept | |
-> void requires _callable_with<Tag, Rcvr, As...> { | |
static_assert(Index::value == 0); | |
Tag()(std::move(rcvr), std::forward<As>(args)...); | |
}; | |
template <class Sndr, class... Env> | |
static constexpr void _check_types() { | |
[]<size_t... Is>(std::index_sequence<Is...>) { | |
((void) get_completion_signatures<_child_type<Sndr, Is>, _FWD_ENV_T<Env>...>(), ...); | |
}(_indices_for<Sndr>()); | |
} | |
}; | |
template<class Tag> | |
struct _impls_for : _default_impls {}; | |
template <class Sndr> | |
using _get_state_t = decltype(_impls_for<tag_of_t<Sndr>>::_get_state); | |
template <class Sndr> | |
using _get_env_t = decltype(_impls_for<tag_of_t<Sndr>>::_get_env); | |
template<class Sndr, class Rcvr> | |
using _state_type = std::decay_t<_call_result_t<_get_state_t<Sndr>, Sndr, Rcvr&>>; | |
template<class Index, class Sndr, class Rcvr> | |
using _env_type = _call_result_t<_get_env_t<Sndr>, Index, _state_type<Sndr, Rcvr>&, const Rcvr&>; | |
template<class Sndr, class Rcvr> | |
struct _basic_state { | |
_basic_state(Sndr&& sndr, Rcvr&& rcvr) | |
: rcvr(std::move(rcvr)) | |
, state(_impls_for<tag_of_t<Sndr>>::_get_state(std::forward<Sndr>(sndr), rcvr)) | |
{ } | |
Rcvr rcvr; | |
_state_type<Sndr, Rcvr> state; | |
}; | |
template<class Sndr, class Rcvr, class Index> | |
requires requires { typename _env_type<Index, Sndr, Rcvr>; } | |
struct _basic_receiver { | |
using receiver_concept = receiver_t; | |
using _tag_t = tag_of_t<Sndr>; | |
using _state_t = _state_type<Sndr, Rcvr>; | |
static constexpr const auto& _complete = _impls_for<_tag_t>::_complete; | |
using _complete_t = decltype(_complete); | |
template<class... As> | |
requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_value_t, As...> | |
void set_value(As&&... args) && noexcept { | |
_complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<As>(args)...); | |
} | |
template<class Error> | |
requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_error_t, Error> | |
void set_error(Error&& err) && noexcept { | |
_complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err)); | |
} | |
void set_stopped() && noexcept | |
requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_stopped_t> { | |
_complete(Index(), op->state, op->rcvr, set_stopped_t()); | |
} | |
auto get_env() const noexcept -> _env_type<Index, Sndr, Rcvr> { | |
return _impls_for<_tag_t>::_get_env(Index(), op->state, op->rcvr); | |
} | |
_basic_state<Sndr, Rcvr>* op; | |
}; | |
inline constexpr struct _connect_all_t { | |
private: | |
template <class Sndr, class Rcvr, size_t... Is> | |
static constexpr auto _mk_connect_fn(_basic_state<Sndr, Rcvr>* op, std::index_sequence<Is...>) noexcept { | |
return [op]<sender_to<_basic_receiver<Sndr, Rcvr, _size_t<Is>>>... Child>(auto, auto&&, Child&&... child) | |
noexcept((_has_nothrow_connect<Child, _basic_receiver<Sndr, Rcvr, _size_t<Is>>> &&...)) { | |
return _tuple{connect(std::forward<Child>(child), _basic_receiver<Sndr, Rcvr, _size_t<Is>>{op})...}; | |
}; | |
} | |
template <class Sndr, class Rcvr> | |
using _connect_fn_t = | |
decltype(_mk_connect_fn(static_cast<_basic_state<Sndr, Rcvr>*>(nullptr), _indices_for<Sndr>())); | |
public: | |
template <class Sndr, class Rcvr> | |
requires _applicable_with<_connect_fn_t<Sndr, Rcvr>, Sndr> | |
constexpr auto operator()(_basic_state<Sndr, Rcvr>* op, Sndr&& sndr) const | |
noexcept(_nothrow_applicable_with<_connect_fn_t<Sndr, Rcvr>, Sndr>) -> decltype(auto) { | |
return _apply(_mk_connect_fn(op, _indices_for<Sndr>()), sndr); | |
} | |
} _connect_all {}; | |
template<class Sndr, class Rcvr> | |
using _connect_all_result = _call_result_t<_connect_all_t, _basic_state<Sndr, Rcvr>*, Sndr>; | |
template <class Sndr, class Rcvr> | |
concept _can_connect = | |
_callable_with<_get_state_t<Sndr>, Sndr, Rcvr&> && | |
_callable_with<_connect_all_t, _basic_state<Sndr, Rcvr>*, Sndr>; | |
template<class Sndr, class Rcvr> | |
requires _can_connect<Sndr, Rcvr> | |
struct _basic_operation : _basic_state<Sndr, Rcvr> { | |
using operation_state_concept = operation_state_t; | |
using _tag_t = tag_of_t<Sndr>; | |
_connect_all_result<Sndr, Rcvr> _inner_ops; | |
_basic_operation(Sndr&& sndr, Rcvr&& rcvr) | |
: _basic_state<Sndr, Rcvr>(std::forward<Sndr>(sndr), std::move(rcvr)) | |
, _inner_ops(_connect_all(this, std::forward<Sndr>(sndr))) | |
{} | |
void start() & noexcept { | |
auto fn = [this](auto&... ops) noexcept { | |
_impls_for<_tag_t>::_start(this->state, this->rcvr, ops...); | |
}; | |
_apply(fn, _inner_ops); | |
} | |
}; | |
#define _CHECK_TYPES _impls_for<Tag>::template _check_types<Self, Env...> | |
template <auto... Values> | |
struct _value_list { | |
static constexpr size_t size = sizeof...(Values); | |
template <size_t I> | |
static constexpr auto value = Values...[I]; | |
}; | |
template <class Tag> | |
struct _basic_sender_base { | |
using sender_concept = sender_t; | |
template <size_t I, class Self> | |
constexpr decltype(auto) get(this Self&& self) noexcept { | |
if constexpr (I == 0) | |
return auto(self.tag); | |
else if constexpr (I == 1) | |
return (std::forward<Self>(self).data); | |
else | |
return (std::forward<Self>(self).*std::remove_reference_t<Self>::_children::template value<I-2>); | |
} | |
template <class Self> | |
decltype(auto) get_env(this const Self& self) noexcept { | |
auto fn = [](auto, auto& data, auto&... child) { | |
return _impls_for<Tag>::_get_attrs(data, child...); | |
}; | |
return _apply(fn, self); | |
} | |
template <receiver Rcvr, _can_connect<Rcvr> Self> | |
auto connect(this Self&& self, Rcvr rcvr) | |
noexcept(std::is_nothrow_constructible_v<_basic_operation<Self, Rcvr>, Self, Rcvr>) | |
{ | |
return _basic_operation<Self, Rcvr>{std::forward<Self>(self), std::move(rcvr)}; | |
} | |
template<class Self, class... Env> | |
requires requires { _CHECK_TYPES(); } | |
static constexpr auto get_completion_signatures() { | |
_CHECK_TYPES(); | |
// don't try to compute the completion signatures if _check_types is not a | |
// core constant expression. | |
if constexpr (requires { requires _is_constant<(_CHECK_TYPES(), true)>; }) { | |
return decltype(_impls_for<Tag>::template _get_complsigs<Self, Env...>())(); | |
} | |
else { | |
return (_CHECK_TYPES(), invalid_completion_signature<struct HERE/**/>()); | |
} | |
} | |
}; | |
template<class Tag, class Data, class... Child> | |
struct _basic_sender; | |
template<class Tag, class Data> | |
struct _basic_sender<Tag, Data> : _basic_sender_base<Tag> { | |
[[no_unique_address]] Tag tag; | |
Data data; | |
using _children = _value_list<>; | |
}; | |
template<class Tag, class Data, class Child0> | |
struct _basic_sender<Tag, Data, Child0> : _basic_sender_base<Tag> { | |
[[no_unique_address]] Tag tag; | |
Data data; | |
Child0 child0; | |
using _children = _value_list<&_basic_sender::child0>; | |
}; | |
template<class Tag, class Data, class Child0, class Child1> | |
struct _basic_sender<Tag, Data, Child0, Child1> : _basic_sender_base<Tag> { | |
[[no_unique_address]] Tag tag; | |
Data data; | |
Child0 child0; | |
Child1 child1; | |
using _children = _value_list<&_basic_sender::child0, &_basic_sender::child1>; | |
}; | |
template<class Tag, class Data, class Child0, class Child1, class Child2> | |
struct _basic_sender<Tag, Data, Child0, Child1, Child2> : _basic_sender_base<Tag> { | |
[[no_unique_address]] Tag tag; | |
Data data; | |
Child0 child0; | |
Child1 child1; | |
Child2 child2; | |
using _children = _value_list<&_basic_sender::child0, &_basic_sender::child1, &_basic_sender::child2>; | |
}; | |
#undef _CHECK_TYPES | |
template <class Tag, class Data, class... Child> | |
constexpr size_t _structured_tuple_size<_basic_sender<Tag, Data, Child...>> = sizeof...(Child) + 2; | |
template<class Tag, class Data = _none_such, class... Child> | |
constexpr auto _make_sender(Tag tag, Data data, Child... child) { | |
using Sender = _basic_sender<Tag, Data, Child...>; | |
if constexpr (!dependent_sender<Sender>) | |
(void) get_completion_signatures<Sender>(); | |
return Sender{{}, tag, data, child...}; | |
} | |
struct _not_a_scheduler {}; | |
// Pipeable sender adaptor CRTP base class | |
template <const auto& Algorithm, size_t... ArgCount> | |
struct _pipeable { | |
static_assert(sizeof...(ArgCount) > 0); | |
template <sender Sndr, class... As> | |
requires ((sizeof...(As) == ArgCount) ||...) | |
constexpr auto operator()(Sndr sndr, As... args) const { | |
return sndr | Algorithm(args...); | |
} | |
template <class... As> | |
requires ((sizeof...(As) == ArgCount) ||...) | |
constexpr auto operator()(As... args) const { | |
if constexpr (sizeof...(As) == 1) { | |
return _closure{args...[0]}; | |
} | |
else { | |
return _closure{_tuple{args...}}; | |
} | |
} | |
private: | |
template <sender Sndr> | |
friend constexpr auto operator|(Sndr sndr, _pipeable) requires ((ArgCount == 0) ||...) { | |
auto dom = _query_or_default(get_domain, sndr, default_domain()); | |
return transform_sender(dom, _make_sender(Algorithm, {}, sndr)); | |
} | |
template <class Data> | |
struct _closure { | |
template <sender Sndr> | |
friend constexpr auto operator|(Sndr sndr, _closure _self) { | |
auto dom = _query_or_default(get_domain, sndr, default_domain()); | |
return transform_sender(dom, _make_sender(Algorithm, std::move(_self.data_), sndr)); | |
} | |
Data data_; | |
}; | |
}; | |
// sender algorithm: just | |
template <const auto& Algorithm> | |
struct IN_ALGORITHM; | |
template <class Algorithm> | |
struct IN_QUERY; | |
template <> | |
struct _impls_for<struct just_t> : _default_impls { | |
static constexpr auto _start = | |
[](auto& state, auto& rcvr) noexcept -> void { | |
auto fn = [&](auto&... ts) noexcept -> void { | |
set_value(std::move(rcvr), std::move(ts)...); | |
}; | |
_apply(fn, state); | |
}; | |
// Non-standard: | |
template <class... Ts> | |
using _value_completions = completion_signatures<set_value_t(Ts...)>; | |
// Non-standard: | |
template <class Sndr, class... Env> | |
static constexpr auto _get_complsigs() { | |
using Completions = std::remove_cvref_t<_data_type<Sndr>>::template _apply<_value_completions>; | |
return Completions(); | |
} | |
}; | |
inline constexpr struct just_t { | |
template <class... Ts> | |
constexpr auto operator()(Ts... ts) const { | |
return _make_sender(just_t{}, _tuple{std::move(ts)...}); | |
} | |
} just{}; | |
// sender algorithm: then | |
extern const struct then_t then; | |
template <> | |
struct _impls_for<then_t> : _default_impls { | |
static constexpr auto _complete = | |
[]<class Tag, class Fn, class... As>(auto, Fn& fn, auto& rcvr, Tag, As&&... args) noexcept { | |
if constexpr (Tag() == set_value) { | |
try { | |
if constexpr (std::same_as<void, std::invoke_result_t<Fn, As...>>) { | |
std::invoke(std::move(fn), std::forward<As>(args)...); | |
set_value(std::move(rcvr)); | |
} | |
else { | |
set_value(std::move(rcvr), std::invoke(std::move(fn), std::forward<As>(args)...)); | |
} | |
} | |
catch(...) { | |
if constexpr (!std::is_nothrow_invocable_v<Fn, As...>) { | |
set_error(std::move(rcvr), std::current_exception()); | |
} | |
} | |
} else { | |
Tag()(std::move(rcvr), std::forward<As>(args)...); | |
} | |
}; | |
// get_completion_signatures for the `then` sender transforms the | |
// value completion signatures by reducing the value types with | |
// the result of Fn when invoked with them. if the function is not | |
// invocable with the given arguments, throw an exception with the | |
// error information. | |
template <class Fn> | |
static constexpr auto transform_sig_fn = | |
[]<class... As>() { | |
if constexpr (!std::invocable<Fn, As...>) { | |
return invalid_completion_signature<IN_ALGORITHM<then>, | |
struct WITH_FUNCTION(Fn), | |
struct WITH_ARGUMENTS(As...)>( | |
"The function passed to std::execution::then is not callable with the" | |
" values sent by the predecessor sender."); | |
} else { | |
constexpr bool _nothrow = std::is_nothrow_invocable_v<Fn, As...>; | |
return completion_signatures{value<std::invoke_result_t<Fn, As...>>} | |
+ eptr_completion_if<!_nothrow>; | |
} | |
}; | |
// Non-standard: | |
template <class Sndr, class... Env> | |
static constexpr auto _get_complsigs() { | |
using Fn = std::remove_cvref_t<_data_type<Sndr>>; | |
auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); | |
return transform_completion_signatures(cs, transform_sig_fn<Fn>); | |
} | |
template <class Sndr, class... Env> | |
static constexpr void _check_types() { | |
(void) _get_complsigs<Sndr, Env...>(); | |
} | |
}; | |
inline constexpr struct then_t : _pipeable<then, 1> {} then {}; | |
// sender algorithm: read_env | |
extern const struct read_env_t read_env; | |
template <> | |
struct _impls_for<read_env_t> : _default_impls { | |
static constexpr auto _start = | |
[](auto query, auto& rcvr) noexcept -> void { | |
try { | |
set_value(std::move(rcvr), query(get_env(rcvr))); | |
} | |
catch(...) { | |
if constexpr (!noexcept(query(get_env(rcvr)))) { | |
set_error(std::move(rcvr), std::current_exception()); | |
} | |
} | |
}; | |
// Non-standard: | |
template<class Sndr, class Env> | |
static constexpr auto _get_complsigs() { | |
using Query = std::decay_t<_data_type<Sndr>>; | |
if constexpr (!_callable_with<Query, Env>) { | |
return invalid_completion_signature<IN_ALGORITHM<read_env>, | |
struct WITH_QUERY(Query), | |
struct WITH_ENVIRONMENT(Env)>( | |
"The receiver's environment does not have a value for the given query."); | |
} | |
else if constexpr (std::same_as<void, _call_result_t<Query, Env>>) { | |
return invalid_completion_signature<IN_ALGORITHM<read_env>, | |
struct WITH_QUERY(Query), | |
struct WITH_ENVIRONMENT(Env)>( | |
"The receiver's environment returned `void` for the given query."); | |
} else { | |
return completion_signatures<set_value_t(_call_result_t<Query, Env>)>(); | |
} | |
} | |
template<class Sndr, class Env> | |
static constexpr void _check_types() { | |
(void) _get_complsigs<Sndr, Env>(); | |
} | |
}; | |
inline constexpr struct read_env_t { | |
template <class Query> | |
constexpr auto operator()(Query query) const noexcept { | |
return _make_sender(read_env, query); | |
} | |
} read_env {}; | |
// let_value, let_error, and let_stopped sender adaptors: | |
extern const struct let_value_t let_value; | |
extern const struct let_error_t let_error; | |
extern const struct let_stopped_t let_stopped; | |
template <const auto& LetAlgorithm> | |
struct _impls_for_let : _default_impls { | |
template <class Fn, class... Env> | |
static constexpr auto transform_sig_fn = | |
[]<class... As>() { | |
if constexpr (!std::invocable<Fn, std::decay_t<As>&...>) { | |
return invalid_completion_signature<IN_ALGORITHM<LetAlgorithm>, | |
struct WITH_FUNCTION(Fn), | |
struct WITH_ARGUMENTS(As...)>( | |
"The function passed to std::execution::[let_value|let_error|let_stopped] is not callable with the" | |
" values sent by the predecessor sender."); | |
} else { | |
using Result = std::invoke_result_t<Fn, std::decay_t<As>&...>; | |
using Error = _sender_type_check_failure<IN_ALGORITHM<LetAlgorithm>, | |
struct WITH_FUNCTION(Fn), | |
struct WITH_ARGUMENTS(As...), | |
struct WITH_RETURN_TYPE(Result), | |
struct WITH_ENVIRONMENT(Env)...>; | |
// BUGBUG: this assumes connect doesn't throw. :-/ | |
constexpr bool Nothrow = _nothrow_decay_copyable<As...> && | |
std::is_nothrow_invocable_v<Fn, std::decay_t<As>&...>; | |
if constexpr (!sender<Result>) { | |
return (throw Error("The function passed to std::execution::[let_value|let_error|let_stopped]" | |
" does not return a sender when called with the values sent by the predecessor sender."), | |
_completions_v<>); | |
} else if constexpr (sizeof...(Env) == 1 && !sender_in<Result, Env...>) { | |
return (throw Error("The function passed to std::execution::[let_value|let_error|let_stopped]" | |
" returned a sender that cannot be used with the current execution environment."), | |
_completions_v<>); | |
} else if constexpr (!_decay_copyable<As...>) { | |
return (throw Error("The value types produced by the predecessor sender cannot be decay " | |
"copied into intermediate storage."), _completions_v<>); | |
} else { | |
return get_completion_signatures<Result, Env...>() + eptr_completion_if<!Nothrow>; | |
} | |
} | |
}; | |
// Non-standard: | |
template <class Sndr, class... Env> | |
static constexpr auto _get_complsigs() { | |
using Fn = std::remove_cvref_t<_data_type<Sndr>>; | |
auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); | |
if constexpr (LetAlgorithm._set_tag == set_value) | |
return transform_completion_signatures(cs, transform_sig_fn<Fn, Env...>, {}, {}); | |
else if constexpr (LetAlgorithm._set_tag == set_error) | |
return transform_completion_signatures(cs, {}, transform_sig_fn<Fn, Env...>, {}); | |
else | |
return transform_completion_signatures(cs, {}, {}, transform_sig_fn<Fn, Env...>); | |
} | |
template <class Sndr, class... Env> | |
static constexpr void _check_types() { | |
(void) _get_complsigs<Sndr, Env...>(); | |
} | |
}; | |
template <> | |
struct _impls_for<let_value_t> : _impls_for_let<let_value> {}; | |
template <> | |
struct _impls_for<let_error_t> : _impls_for_let<let_error> {}; | |
template <> | |
struct _impls_for<let_stopped_t> : _impls_for_let<let_stopped> {}; | |
inline constexpr struct let_value_t : _pipeable<let_value, 1> { | |
static constexpr const auto& _set_tag = set_value; | |
} let_value {}; | |
inline constexpr struct let_error_t : _pipeable<let_error, 1> { | |
static constexpr const auto& _set_tag = set_error; | |
} let_error {}; | |
inline constexpr struct let_stopped_t : _pipeable<let_stopped, 1> { | |
static constexpr const auto& _set_tag = set_stopped; | |
} let_stopped {}; | |
/// stopped_as_optional | |
extern const struct stopped_as_optional_t stopped_as_optional; | |
template <> | |
struct _impls_for<stopped_as_optional_t> : _default_impls { | |
template <class... Ts> | |
using _value_sig = set_value_t(Ts...); | |
template<class Sndr, class... Env> | |
static constexpr void _check_types() { | |
auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); | |
using ValueSigs = _value_types<decltype(cs), _value_sig, completion_signatures>; | |
static constexpr char what[] = "The predecessor sender of std::execution::stopped_as_optional" | |
" must have exactly one value completion signatures with 1 or more values."; | |
if constexpr (!requires { typename _single_sender_value_t<_child_type<Sndr>, Env...>; }) { | |
(void) invalid_completion_signature<IN_ALGORITHM<stopped_as_optional>, | |
struct WITH_VALUE_COMPLETIONS(ValueSigs)>(what); | |
} | |
else if constexpr (std::same_as<void, _single_sender_value_t<_child_type<Sndr>, Env...>>) { | |
(void) invalid_completion_signature<IN_ALGORITHM<stopped_as_optional>, | |
struct WITH_VALUE_COMPLETIONS(ValueSigs)>(what); | |
} | |
} | |
// Non-standard: | |
template<class Sndr, class... Env> | |
static constexpr auto _get_complsigs() { | |
using V = _single_sender_value_t<_child_type<Sndr>, Env...>; | |
auto cs = get_completion_signatures<_child_type<Sndr>, _FWD_ENV_T<Env>...>(); | |
return transform_completion_signatures(cs, | |
[]<class...>(){ return _completions_v<set_value_t(std::optional<V>)>; }, | |
{}, | |
[] { return _completions_v<set_value_t(std::optional<V>)>; }); | |
} | |
}; | |
inline constexpr struct stopped_as_optional_t : _pipeable<stopped_as_optional, 0> { | |
template <_sender_for<stopped_as_optional_t> Sndr, class Env> | |
constexpr auto transform_sender(Sndr&& sndr, const Env& env) { | |
if constexpr (!sender_in<_child_type<Sndr>, Env>) { | |
return _not_a_sender<[]{ (void) get_completion_signatures<_child_type<Sndr>, Env>(); }>(); | |
} | |
else { | |
auto&& [_, _, child] = sndr; | |
using V = _single_sender_value_t<_child_type<Sndr>, Env>; | |
return let_stopped( | |
then(std::forward_like<Sndr>(child), | |
[]<class... Ts>(Ts&&... ts) noexcept(std::is_nothrow_constructible_v<V, Ts...>) { | |
return std::optional<V>(std::in_place, std::forward<Ts>(ts)...); | |
}), | |
[]() noexcept { return just(std::optional<V>()); }); | |
} | |
} | |
} stopped_as_optional {}; | |
// Some simple tests: | |
template <class... Sigs> | |
struct test_sender { | |
using sender_concept = sender_t; | |
using completion_signatures = ::completion_signatures<Sigs...>; | |
}; | |
template <class... Sigs> | |
struct test_dependent_sender : sender_t { | |
template <class, class Env> | |
static constexpr auto get_completion_signatures() { | |
return ::completion_signatures<Sigs...>(); | |
} | |
}; | |
void test_stop_as_optional() { | |
{ | |
auto sndr = just(42) | stopped_as_optional(); | |
using Sndr = decltype(sndr); | |
static_assert(sender_in<Sndr>); | |
auto cs = get_completion_signatures<Sndr>(); | |
static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); | |
} | |
{ | |
auto sndr = just(42, 3.14) | stopped_as_optional(); | |
using Sndr = decltype(sndr); | |
static_assert(sender_in<Sndr>); | |
auto cs = get_completion_signatures<Sndr>(); | |
static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<std::tuple<int, double>>)>>); | |
} | |
{ | |
test_sender<set_value_t(int), set_stopped_t()> s0; | |
auto sndr = s0 | stopped_as_optional(); | |
using Sndr = decltype(sndr); | |
static_assert(sender_in<Sndr>); | |
auto cs = get_completion_signatures<Sndr>(); | |
static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); | |
} | |
{ | |
test_dependent_sender<set_value_t(int), set_stopped_t()> s0; | |
auto sndr = s0 | stopped_as_optional(); | |
using Sndr = decltype(sndr); | |
static_assert(!sender_in<Sndr>); | |
auto cs = get_completion_signatures<Sndr, env<>>(); | |
static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); | |
} | |
{ | |
auto sndr = read_env(get_domain) | stopped_as_optional(); | |
using Sndr = decltype(sndr); | |
static_assert(!sender_in<Sndr>); | |
static_assert(!sender_in<Sndr, env<>>); | |
// auto cs = get_completion_signatures<Sndr, env<>>(); | |
} | |
} | |
struct sink_receiver : receiver_t { | |
template <class... As> | |
void set_value(As&&... args) noexcept { | |
((std::cout << "Done! Result: ") <<...<< args) << '\n'; | |
} | |
template <class Err> | |
void set_error(Err&& err) noexcept { | |
} | |
void set_stopped() noexcept { | |
} | |
}; | |
int main(int argc, char* argv[]) { | |
auto op = connect(just(42, 43) | then([](int a, int b) {return a+b;}), sink_receiver{}); | |
start(op); | |
auto op2 = connect(std::suspend_never{}, sink_receiver{}); | |
start(op2); | |
{ | |
auto x = just() | let_value([]() noexcept { return just(0,0); }); | |
using X = decltype(x); | |
static_assert(sender<X>); | |
static_assert(sender_in<X>); | |
static_assert(!dependent_sender<X>); | |
} | |
auto x = just(argc, 0) | |
| then([](int, int){return 0;}) | |
| then([](int i){return "hello world";}); | |
static_assert(!dependent_sender<decltype(x)>); | |
auto [tag, fun, child] = just(0,0) | then([](int,int){}); | |
#ifdef ERROR | |
auto y = just(0, 0) | |
| then([](int, int*){return 0;}) | |
| then([](int i){return "hello world";}); | |
#endif | |
auto z = read_env(get_domain); | |
auto a = read_env(get_domain) | then([](){}); // OK | |
static_assert(sender<decltype(z)>); | |
static_assert(dependent_sender<decltype(z)>); | |
static_assert(!sender_in<decltype(z), int>); | |
static_assert(sender_in<decltype(z), decltype(prop{get_domain, default_domain()})>); | |
static_assert(!sender_in<decltype(z)>); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment