Skip to content

Instantly share code, notes, and snippets.

@ericniebler
Last active February 2, 2025 00:11
Show Gist options
  • Save ericniebler/0896776ab1c8f5b7f77d7094c0400df5 to your computer and use it in GitHub Desktop.
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
#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