Commit dad6c2b8 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

make awaitables sender and make single-typed senders awaitable

Summary:
Add coroutine support to pushmi by making all sender types that satisfy the `SingleTypedSender` concept awaitable; that is, you can get their result by `co_await`-ing them in a coroutine.

In addition, all Awaitable types automatically satisfy the `SingleTypedSender` concept; that is, you can pass them to `pushmi::submit` along with a `Receiver` that is invoked with the result of `co_await`-ing the awaitable.

Reviewed By: kirkshoop

Differential Revision: D14688373

fbshipit-source-id: 8598b303aebcaa070033868abd440245c165bd73
parent 432d4332
......@@ -50,7 +50,7 @@ it is this convolution that creates the race between the producer and consumer t
The `receiver` type in the library provides simple ways to construct new implementations of the Receiver concept.
construct a sink type that accepts any value or error type (and aborts on error)
construct a receiver_tag type that accepts any value or error type (and aborts on error)
```cpp
receiver<> s;
......
......@@ -22,6 +22,7 @@
#include <folly/experimental/pushmi/forwards.h>
#include <folly/experimental/pushmi/properties.h>
#include <folly/experimental/pushmi/tags.h>
#include <folly/experimental/pushmi/detail/sender_concepts.h>
#include <folly/experimental/pushmi/traits.h>
namespace folly {
......@@ -92,162 +93,11 @@ PUSHMI_CONCEPT_DEF(
// add concepts to support senders
//
namespace detail {
template<template<template<class...> class, template<class...> class> class>
struct test_value_types;
template<template<template<class...> class> class>
struct test_error_type;
PUSHMI_CONCEPT_DEF(
template(class S)
concept SenderLike_,
True<typename S::sender_category>
);
PUSHMI_CONCEPT_DEF(
template(class S)
concept TypedSenderLike_,
SenderLike_<S> &&
True<test_value_types<S::template value_types>> &&
True<test_error_type<S::template error_type>>
);
template<class, class = void>
struct basic_sender_traits {
};
template<class S>
struct basic_typed_sender_traits : awaitable_senders::sender_adl_hook {
template<template<class...> class Tuple, template<class...> class Variant>
using value_types = typename S::template value_types<Tuple, Variant>;
template<template<class...> class Variant>
using error_type = typename S::template error_type<Variant>;
};
template<class S>
struct basic_sender_traits<S, std::enable_if_t<SenderLike_<S>>>
: std::conditional_t<
TypedSenderLike_<S>,
basic_typed_sender_traits<S>,
awaitable_senders::sender_adl_hook> {
using sender_category = typename S::sender_category;
};
} // namespace detail
template<typename S, typename>
struct sender_traits
: std::conditional_t<
std::is_same<std::decay_t<S>, S>::value,
detail::basic_sender_traits<S>,
sender_traits<std::decay_t<S>>> {
using _not_specialized = void;
};
// A Sender is, by default, a "many" sender, in that it may call
// set_value on its Receiver multiple times before calling set_done.
PUSHMI_CONCEPT_DEF(
template(class S)
concept Sender,
SemiMovable<remove_cvref_t<S>> &&
True<typename sender_traits<S>::sender_category> &&
DerivedFrom<typename sender_traits<S>::sender_category, sender_tag>
);
template<class S>
PUSHMI_PP_CONSTRAINED_USING(
Sender<S>,
sender_category_t =,
typename sender_traits<S>::sender_category
);
// A single sender is a special kind of sender that promises to only
// call set_value once (or not at all) before calling set_done.
PUSHMI_CONCEPT_DEF(
template(class S)
concept SingleSender,
Sender<S> &&
DerivedFrom<typename sender_traits<S>::sender_category, single_sender_tag>
);
PUSHMI_CONCEPT_DEF(
template(class S)
concept TypedSender,
Sender<S> &&
True<detail::test_value_types<sender_traits<S>::template value_types>> &&
True<detail::test_error_type<sender_traits<S>::template error_type>>
);
template<
class From,
template<class...> class Tuple,
template<class...> class Variant = detail::identity_t>
PUSHMI_PP_CONSTRAINED_USING(
TypedSender<From>,
sender_values_t =,
typename sender_traits<remove_cvref_t<From>>::
template value_types<Tuple, Variant>
);
template<class From, template<class...> class Variant = detail::identity_t>
PUSHMI_PP_CONSTRAINED_USING(
TypedSender<From>,
sender_error_t =,
typename sender_traits<remove_cvref_t<From>>::
template error_type<Variant>
);
namespace detail {
template<class... Ts>
using count_values = std::integral_constant<std::size_t, sizeof...(Ts)>;
} // namespace detail
PUSHMI_CONCEPT_DEF(
template(class S)
concept SingleTypedSender,
TypedSender<S> &&
SingleSender<S> &&
(sender_values_t<S, detail::count_values>::value <= 1u)
);
// /// \cond
// template<class Fun>
// struct __invoke_with {
// template<typename...Args>
// using result_t = std::invoke_result_t<Fun, Args...>;
// template<template<class...> class Tuple>
// struct as {
// template<typename...Args>
// using result_t =
// std::conditional_t<
// std::is_void_v<result_t<Args...>>,
// Tuple<>,
// Tuple<result_t<Args...>>>;
// };
// };
// /// \endcond
//
// template<class Fun, TypedSender From>
// requires requires {
// typename sender_traits<From>::template value_types<
// __invoke_with<Fun>::template result_t, __typelist>;
// }
// struct transformed_sender_of : sender_base<sender_category_t<From>> {
// template<template<class...> class Variant = identity_t>
// using error_type = sender_error_t<Variant>;
// template<
// template<class...> class Tuple,
// template<class...> class Variant = identity_t>
// using value_types =
// sender_values_t<
// From,
// __invoke_with<Fun>::template as<Tuple>::template result_t,
// Variant>;
// };
PUSHMI_CONCEPT_DEF(
template(class S, class R) //
(concept SenderTo)(S, R), //
requires(S&& s, R&& r) //
(submit((S &&) s, (R &&) r)) &&
(pushmi::submit((S &&) s, (R &&) r)) &&
Sender<S> && Receiver<R>);
// is_always_blocking trait and tag
......@@ -444,3 +294,6 @@ PUSHMI_CONCEPT_DEF(
} // namespace pushmi
} // namespace folly
// Make all single typed senders also awaitable:
#include <folly/experimental/pushmi/detail/sender_adapter.h>
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <utility>
#include <type_traits>
#include <folly/Traits.h>
#if defined(__cpp_coroutines)
#include <folly/experimental/coro/Traits.h>
#endif
#include <folly/experimental/pushmi/detail/concept_def.h>
#include <folly/experimental/pushmi/forwards.h>
#include <folly/experimental/pushmi/detail/sender_concepts.h>
#include <folly/experimental/pushmi/traits.h>
namespace folly {
namespace pushmi {
#if defined(__cpp_coroutines)
namespace detail {
PUSHMI_CONCEPT_DEF(
template(typename Tp)
concept AwaitableLike_,
SemiMovable<Tp> && coro::is_awaitable_v<Tp>
);
template<typename T>
struct awaitable_sender_traits_impl : awaitable_senders::sender_adl_hook {
template<template<class...> class Tuple, template<class...> class Variant>
using value_types = Variant<Tuple<T>>;
template<template<class...> class Variant>
using error_type = Variant<std::exception_ptr>;
};
template<>
struct awaitable_sender_traits_impl<void> : awaitable_senders::sender_adl_hook {
template<template<class...> class Tuple, template<class...> class Variant>
using value_types = Variant<Tuple<>>;
template<template<class...> class Variant>
using error_type = Variant<std::exception_ptr>;
};
template<class T>
struct IsAwaitableLike_
: std::integral_constant<bool, detail::AwaitableLike_<T>> {
};
std::true_type safe_to_test_awaitable(void const*);
template<
typename T,
// Don't ask if a type is awaitable if it inherits from
// awaitable_senders::sender_adl_hook, because that will find the
// default operator co_await that is constrained with TypedSingleSender
// and cause a recursive template instantiation.
bool = Conjunction<
decltype(safe_to_test_awaitable(static_cast<T*>(nullptr))),
IsAwaitableLike_<T>>::value>
struct awaitable_sender_traits {
};
template<typename T>
struct awaitable_sender_traits<T, true>
: awaitable_sender_traits_impl<coro::await_result_t<T>> {
using sender_category = single_sender_tag;
};
} // namespace detail
PUSHMI_CONCEPT_DEF(
template(typename Tp)
concept Awaiter,
coro::is_awaiter_v<Tp>
);
PUSHMI_CONCEPT_DEF(
template(typename Tp)
concept Awaitable,
detail::AwaitableLike_<Tp> &&
SingleTypedSender<Tp>
);
PUSHMI_CONCEPT_DEF(
template(typename Tp, typename Result)
concept AwaitableOf,
Awaitable<Tp> && ConvertibleTo<coro::await_result_t<Tp>, Result>
);
#else // if defined(__cpp_coroutines)
namespace detail {
template <class>
using awaitable_sender_traits = awaitable_senders::sender_adl_hook;
} // namespace detail
#endif // if defined(__cpp_coroutines)
namespace detail {
template<class S>
struct basic_typed_sender_traits : awaitable_senders::sender_adl_hook {
template<template<class...> class Tuple, template<class...> class Variant>
using value_types = typename S::template value_types<Tuple, Variant>;
template<template<class...> class Variant>
using error_type = typename S::template error_type<Variant>;
};
template<class S, bool = SenderLike_<S>>
struct basic_sender_traits : awaitable_sender_traits<S> {
};
template<class S>
struct basic_sender_traits<S, true>
: std::conditional_t<
TypedSenderLike_<S>,
basic_typed_sender_traits<S>,
awaitable_sender_traits<S>> {
using sender_category = typename S::sender_category;
};
} // namespace detail
template<typename S, typename>
struct sender_traits
: std::conditional_t<
std::is_same<std::decay_t<S>, S>::value,
detail::basic_sender_traits<S>,
sender_traits<std::decay_t<S>>> {
using _not_specialized = void;
};
} // namespace pushmi
} // namespace folly
......@@ -520,11 +520,6 @@ struct Not {
explicit constexpr operator bool() const noexcept {
return !static_cast<bool>(T{});
}
PUSHMI_TEMPLATE(class This = Not, bool B)
(requires B == static_cast<bool>(This{})) constexpr
operator std::integral_constant<bool, B>() const noexcept {
return {};
}
constexpr auto operator!() const noexcept {
return T{};
}
......@@ -543,11 +538,6 @@ struct And {
explicit constexpr operator bool() const noexcept {
return static_cast<bool>(std::conditional_t<static_cast<bool>(T{}), U, std::false_type>{});
}
PUSHMI_TEMPLATE(class This = And, bool B) //
(requires B == static_cast<bool>(This{})) //
constexpr operator std::integral_constant<bool, B>() const noexcept {
return {};
}
constexpr auto operator!() const noexcept {
return Not<And>{};
}
......@@ -566,11 +556,6 @@ struct Or {
explicit constexpr operator bool() const noexcept {
return static_cast<bool>(std::conditional_t<static_cast<bool>(T{}), std::true_type, U>{});
}
PUSHMI_TEMPLATE(class This = Or, bool B) //
(requires B == static_cast<bool>(This{})) //
constexpr operator std::integral_constant<bool, B>() const noexcept {
return {};
}
constexpr auto operator!() const noexcept {
return Not<Or>{};
}
......
......@@ -129,11 +129,15 @@ struct id_fn {
#include <type_traits>
#define PUSHMI_IF_CONSTEXPR(LIST)\
PUSHMI_PP_IGNORE_SHADOW_BEGIN \
PUSHMI_EVAL(PUSHMI_IF_CONSTEXPR_ELSE_, PUSHMI_IF_CONSTEXPR_IF_ LIST)\
PUSHMI_PP_IGNORE_SHADOW_END \
/**/
#define PUSHMI_IF_CONSTEXPR_RETURN(LIST)\
PUSHMI_PP_IGNORE_SHADOW_BEGIN \
return PUSHMI_EVAL(PUSHMI_IF_CONSTEXPR_ELSE_, PUSHMI_IF_CONSTEXPR_IF_ LIST)\
PUSHMI_PP_IGNORE_SHADOW_END \
/**/
#define PUSHMI_IF_CONSTEXPR_IF_(...) \
......
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cassert>
#include <exception>
#if defined(__cpp_coroutines)
#include <experimental/coroutine>
#endif
#include <type_traits>
#include <utility>
#if defined(__cpp_coroutines)
#include <folly/experimental/coro/detail/ManualLifetime.h>
#endif
#include <folly/experimental/pushmi/detail/awaitable_concepts.h>
#include <folly/experimental/pushmi/detail/sender_concepts.h>
#include <folly/experimental/pushmi/extension_points.h>
#include <folly/experimental/pushmi/traits.h>
#include <folly/experimental/pushmi/tags.h>
namespace folly {
namespace pushmi {
struct operation_cancelled : std::exception {
virtual const char* what() const noexcept {
return "operation cancelled";
}
};
#if defined(__cpp_coroutines)
PUSHMI_TEMPLATE(class From)( //
requires SingleTypedSender<From> //
) //
struct sender_awaiter {
private:
using value_type = sender_values_t<From, detail::identity_or_void_t>;
using coro_handle = std::experimental::coroutine_handle<>;
std::add_pointer_t<From> sender_{};
enum class state { empty, value, exception };
coro_handle continuation_{};
state state_ = state::empty;
union {
coro::detail::ManualLifetime<value_type> value_{};
coro::detail::ManualLifetime<std::exception_ptr> exception_;
};
using is_always_blocking = property_query<From, is_always_blocking<>>;
struct internal_receiver {
sender_awaiter *this_;
using receiver_category = receiver_tag;
PUSHMI_TEMPLATE(class U)( //
requires ConvertibleTo<U, value_type> //
) //
void value(U&& value)
noexcept(std::is_nothrow_constructible_v<value_type, U>) {
this_->value_.construct(static_cast<U&&>(value));
this_->state_ = state::value;
}
PUSHMI_TEMPLATE(class V = value_type)( //
requires std::is_void<V>::value //
) //
void value() noexcept {
this_->value_.construct();
this_->state_ = state::value;
}
void done() noexcept {
if (!is_always_blocking::value)
this_->continuation_.resume();
}
template<typename Error>
void error(Error error) noexcept {
assert(this_->state_ != state::value);
PUSHMI_IF_CONSTEXPR( (std::is_same_v<Error, std::exception_ptr>) (
this_->exception_.construct(std::move(error));
) else (
this_->exception_.construct(std::make_exception_ptr(std::move(error)));
))
this_->state_ = state::exception;
if (!is_always_blocking::value)
this_->continuation_.resume();
}
};
public:
sender_awaiter() {}
sender_awaiter(From&& sender) noexcept
: sender_(std::addressof(sender))
{}
sender_awaiter(sender_awaiter &&that)
noexcept(std::is_nothrow_move_constructible_v<value_type> ||
std::is_void_v<value_type>)
: sender_(std::exchange(that.sender_, nullptr))
, continuation_{std::exchange(that.continuation_, {})}
, state_(that.state_) {
if (that.state_ == state::value) {
PUSHMI_IF_CONSTEXPR( (!std::is_void_v<value_type>) (
id(value_).construct(std::move(that.value_).get());
) else (
))
that.value_.destruct();
that.state_ = state::empty;
} else if (that.state_ == state::exception) {
exception_.construct(std::move(that.exception_).get());
that.exception_.destruct();
that.state_ = state::empty;
}
}
~sender_awaiter() {
if (state_ == state::value) {
value_.destruct();
} else if (state_ == state::exception) {
exception_.destruct();
}
}
static constexpr bool await_ready() noexcept {
return false;
}
// Add detection and handling of blocking completion here, and
// return 'false' from await_suspend() in that case rather than
// potentially recursively resuming the awaiting coroutine which
// could eventually lead to a stack-overflow.
using await_suspend_result_t =
std::conditional_t<is_always_blocking::value, bool, void>;
await_suspend_result_t await_suspend(coro_handle continuation) noexcept {
continuation_ = continuation;
pushmi::submit(static_cast<From&&>(*sender_), internal_receiver{this});
return await_suspend_result_t(); // return false or void
}
decltype(auto) await_resume() {
if (state_ == state::exception) {
std::rethrow_exception(std::move(exception_).get());
} else if (state_ == state::empty) {
throw operation_cancelled{};
} else {
return std::move(value_).get();
}
}
};
#if __cpp_deduction_guides >= 201703
template<typename From>
sender_awaiter(From&&) -> sender_awaiter<From>;
#endif
namespace awaitable_senders
{
// Any TypedSender that inherits from `sender` or `sender_traits` is
// automatically awaitable with the following operator co_await through the
// magic of associated namespaces. To make any other TypedSender awaitable,
// from within the body of the awaiting coroutine, do:
// `using namespace ::folly::pushmi::awaitable_senders;`
PUSHMI_TEMPLATE(class From)( //
requires not lazy::Awaiter<From> && lazy::SingleTypedSender<From> //
) //
sender_awaiter<From> operator co_await(From&& from)
{
return static_cast<From&&>(from);
}
}
#endif
} // namespace pushmi
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <utility>
#include <type_traits>
#include <folly/experimental/pushmi/detail/concept_def.h>
#include <folly/experimental/pushmi/forwards.h>
#include <folly/experimental/pushmi/traits.h>
namespace folly {
namespace pushmi {
namespace detail {
template<template<template<class...> class, template<class...> class> class>
struct test_value_types;
template<template<template<class...> class> class>
struct test_error_type;
PUSHMI_CONCEPT_DEF(
template(class S)
concept SenderLike_,
True<typename S::sender_category>
);
PUSHMI_CONCEPT_DEF(
template(class S)
concept TypedSenderLike_,
SenderLike_<S> &&
True<test_value_types<S::template value_types>> &&
True<test_error_type<S::template error_type>>
);
} // namespace detail
PUSHMI_CONCEPT_DEF(
template(class S)
concept Sender,
SemiMovable<remove_cvref_t<S>> &&
True<typename sender_traits<S>::sender_category> &&
DerivedFrom<typename sender_traits<S>::sender_category, sender_tag>
);
template<class S>
PUSHMI_PP_CONSTRAINED_USING(
Sender<S>,
sender_category_t =,
typename sender_traits<S>::sender_category
);
PUSHMI_CONCEPT_DEF(
template(class S)
concept SingleSender,
Sender<S> &&
DerivedFrom<typename sender_traits<S>::sender_category, single_sender_tag>
);
PUSHMI_CONCEPT_DEF(
template(class S)
concept TypedSender,
Sender<S> &&
True<detail::test_value_types<sender_traits<S>::template value_types>> &&
True<detail::test_error_type<sender_traits<S>::template error_type>>
);
template<
class From,
template<class...> class Tuple,
template<class...> class Variant = detail::identity_t>
PUSHMI_PP_CONSTRAINED_USING(
TypedSender<From>,
sender_values_t =,
typename sender_traits<remove_cvref_t<From>>::
template value_types<Tuple, Variant>
);
template<class From, template<class...> class Variant = detail::identity_t>
PUSHMI_PP_CONSTRAINED_USING(
TypedSender<From>,
sender_error_t =,
typename sender_traits<remove_cvref_t<From>>::
template error_type<Variant>
);
namespace detail {
template<class... Ts>
using count_values = std::integral_constant<std::size_t, sizeof...(Ts)>;
} // namespace detail
PUSHMI_CONCEPT_DEF(
template(class S)
concept SingleTypedSender,
TypedSender<S> &&
SingleSender<S> &&
(sender_values_t<S, detail::count_values>::value <= 1u)
);
// /// \cond
// template<class Fun>
// struct __invoke_with {
// template<typename...Args>
// using result_t = std::invoke_result_t<Fun, Args...>;
// template<template<class...> class Tuple>
// struct as {
// template<typename...Args>
// using result_t =
// std::conditional_t<
// std::is_void_v<result_t<Args...>>,
// Tuple<>,
// Tuple<result_t<Args...>>>;
// };
// };
// /// \endcond
//
// template<class Fun, TypedSender From>
// requires requires {
// typename sender_traits<From>::template value_types<
// __invoke_with<Fun>::template result_t, __typelist>;
// }
// struct transformed_sender_of : sender<sender_category_t<From>> {
// template<template<class...> class Variant = identity_t>
// using error_type = sender_error_t<Variant>;
// template<
// template<class...> class Tuple,
// template<class...> class Variant = identity_t>
// using value_types =
// sender_values_t<
// From,
// __invoke_with<Fun>::template as<Tuple>::template result_t,
// Variant>;
// };
} // namespace pushmi
} // namespace folly
......@@ -17,15 +17,21 @@
#include <future>
#include <folly/experimental/pushmi/detail/awaitable_concepts.h>
#include <folly/experimental/pushmi/detail/functional.h>
#include <folly/experimental/pushmi/detail/if_constexpr.h>
#include <folly/experimental/pushmi/detail/sender_concepts.h>
#include <folly/experimental/pushmi/forwards.h>
#include <folly/experimental/pushmi/properties.h>
#include <folly/experimental/pushmi/tags.h>
#include <folly/experimental/pushmi/traits.h>
#include <folly/CppAttributes.h>
#include <folly/Portability.h>
#include <folly/lang/CustomizationPoint.h>
namespace folly {
namespace pushmi {
namespace __adl {
namespace _adl {
//
// support methods on a class reference
......@@ -77,13 +83,13 @@ auto make_strand(SD& sd) //
return sd.make_strand();
}
PUSHMI_TEMPLATE(class SD, class Out)
(requires //
requires(std::declval<SD>().submit(std::declval<Out>()))) //
void submit(SD&& sd, Out&& out) //
noexcept(noexcept(((SD &&) sd).submit((Out &&) out))) {
((SD &&) sd).submit((Out &&) out);
}
// PUSHMI_TEMPLATE(class SD, class Out)
// (requires //
// requires(std::declval<SD>().submit(std::declval<Out>()))) //
// void submit(SD&& sd, Out&& out) //
// noexcept(noexcept(((SD &&) sd).submit((Out &&) out))) {
// ((SD &&) sd).submit((Out &&) out);
// }
PUSHMI_TEMPLATE(class SD)
(requires //
......@@ -160,13 +166,13 @@ auto make_strand(SD&& sd) //
return make_strand(*sd);
}
PUSHMI_TEMPLATE(class SD, class Out)
(requires //
requires(submit(*std::declval<SD>(), std::declval<Out>()))) //
void submit(SD&& sd, Out&& out, ...) // MSVC(use ... to disambiguate)
noexcept(noexcept(submit(*sd, (Out &&) out))) {
submit(*sd, (Out &&) out);
}
// PUSHMI_TEMPLATE(class SD, class Out)
// (requires //
// requires(submit(*std::declval<SD>(), std::declval<Out>()))) //
// void submit(SD&& sd, Out&& out, ...) // MSVC(use ... to disambiguate)
// noexcept(noexcept(submit(*sd, (Out &&) out))) {
// submit(*sd, (Out &&) out);
// }
PUSHMI_TEMPLATE(class SD)
(requires //
......@@ -320,14 +326,14 @@ struct make_strand_fn {
}
};
struct do_submit_fn {
PUSHMI_TEMPLATE(class SD, class Out)
(requires requires(submit(std::declval<SD>(), std::declval<Out>()))) //
void operator()(SD&& s, Out&& out) const //
noexcept(noexcept(submit((SD &&) s, (Out &&) out))) {
submit((SD &&) s, (Out &&) out);
}
};
// struct do_submit_fn {
// PUSHMI_TEMPLATE(class SD, class Out)
// (requires requires(submit(std::declval<SD>(), std::declval<Out>()))) //
// void operator()(SD&& s, Out&& out) const //
// noexcept(noexcept(submit((SD &&) s, (Out &&) out))) {
// submit((SD &&) s, (Out &&) out);
// }
// };
struct do_schedule_fn {
PUSHMI_TEMPLATE(class SD, class... VN)
......@@ -349,19 +355,20 @@ struct get_top_fn {
}
};
} // namespace __adl
} // namespace _adl
PUSHMI_INLINE_VAR constexpr __adl::set_done_fn set_done{};
PUSHMI_INLINE_VAR constexpr __adl::set_error_fn set_error{};
PUSHMI_INLINE_VAR constexpr __adl::set_value_fn set_value{};
PUSHMI_INLINE_VAR constexpr __adl::set_starting_fn set_starting{};
PUSHMI_INLINE_VAR constexpr __adl::get_executor_fn get_executor{};
PUSHMI_INLINE_VAR constexpr __adl::make_strand_fn make_strand{};
PUSHMI_INLINE_VAR constexpr __adl::do_submit_fn submit{};
PUSHMI_INLINE_VAR constexpr __adl::do_schedule_fn schedule{};
PUSHMI_INLINE_VAR constexpr __adl::get_top_fn now{};
PUSHMI_INLINE_VAR constexpr __adl::get_top_fn top{};
FOLLY_DEFINE_CPO(_adl::set_done_fn, set_done)
FOLLY_DEFINE_CPO(_adl::set_error_fn, set_error)
FOLLY_DEFINE_CPO(_adl::set_value_fn, set_value)
FOLLY_DEFINE_CPO(_adl::set_starting_fn, set_starting)
FOLLY_DEFINE_CPO(_adl::get_executor_fn, get_executor)
FOLLY_DEFINE_CPO(_adl::make_strand_fn, make_strand)
//FOLLY_DEFINE_CPO(_adl::do_submit_fn, submit)
FOLLY_DEFINE_CPO(_adl::do_schedule_fn, schedule)
FOLLY_DEFINE_CPO(_adl::get_top_fn, now)
FOLLY_DEFINE_CPO(_adl::get_top_fn, top)
/// A std::promise is a valid Receiver:
template <class T>
struct receiver_traits<std::promise<T>> {
using receiver_category = receiver_tag;
......@@ -371,5 +378,158 @@ struct receiver_traits<std::promise<void>> {
using receiver_category = receiver_tag;
};
namespace _submit_adl
{
template <class S, class R>
void submit(S&&, R&&) = delete;
template <class T>
constexpr typename T::value_type const _v = T::value;
PUSHMI_CONCEPT_DEF(
template(class S, class R)
concept HasMemberSubmit_,
requires (S&& from, R&& to) (
((S&&) from).submit((R&&) to),
// requires_<noexcept(((S&&) from).submit((R&&) to))>,
requires_<std::is_void<decltype(((S&&) from).submit((R&&) to))>::value>
)
);
PUSHMI_CONCEPT_DEF(
template(class S, class R)
concept HasNonMemberSubmit_,
requires (S&& from, R&& to) (
submit((S&&) from, (R&&) to),
// requires_<noexcept(submit((S&&) from, (R&&) to))>,
requires_<std::is_void<decltype(submit((S&&) from, (R&&) to))>::value>
)
);
#if defined(__cpp_coroutines)
PUSHMI_CONCEPT_DEF(
template (class A, class R)
concept IsSubmittableAwaitable_,
not detail::SenderLike_<A> && Awaitable<A> //&&
//ReceiveError<R, std::exception_ptr> &&
//ReceiveValue<R, coro::await_result_t<A>>
);
#endif
struct _fn
{
private:
#if defined(__cpp_coroutines)
// See detail/awaitable_adapter.hpp for implementation.
template<class A, class R>
static void _submit_awaitable_(A awaitable, R to) noexcept;
#endif
struct _impl_ {
PUSHMI_TEMPLATE(class S, class R)
(requires Sender<S>) // && Receiver<R>)
auto operator()(S&& from, R&& to) const
{
// Prefer a .submit() member if it exists:
PUSHMI_IF_CONSTEXPR_RETURN((HasMemberSubmit_<S, R>) (
id((S&&) from).submit((R&&) to);
return std::true_type{};
) else (
// Otherwise, dispatch to a submit() free function if it exists:
PUSHMI_IF_CONSTEXPR_RETURN((HasNonMemberSubmit_<S, R>) (
submit(id((S&&) from), (R&&) to);
return std::true_type{};
) else (
#if defined(__cpp_coroutines)
// Otherwise, if we support coroutines and S looks like an
// awaitable, dispatch to the
PUSHMI_IF_CONSTEXPR_RETURN((IsSubmittableAwaitable_<S, R>) (
_submit_awaitable_(id((S&&) from), (R&&) to);
return std::true_type{};
) else (
return std::false_type{};
))
#else
return std::false_type{};
#endif
))
))
}
};
public:
PUSHMI_TEMPLATE(class S, class R)
(requires Invocable<_impl_, S, R> && invoke_result_t<_impl_, S, R>::value)
constexpr void operator()(S&& from, R&& to) const {
(void) _impl_{}((S&&) from, (R&&) to);
}
};
#if defined(__cpp_coroutines)
struct FOLLY_MAYBE_UNUSED oneway_task
{
struct promise_type
{
oneway_task get_return_object() noexcept { return {}; }
std::experimental::suspend_never initial_suspend() noexcept { return {}; }
std::experimental::suspend_never final_suspend() noexcept { return {}; }
void return_void() noexcept {}
[[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
};
};
#endif
#if defined(__cpp_coroutines)
// Make all awaitables senders:
template<class A, class R>
void _fn::_submit_awaitable_(A awaitable, R to) noexcept
{
// TRICKY: We want to make sure that if copying/moving 'awaitable' throws
// or if allocating the coroutine frame throws that we can still call
// op::set_error() on the receiver. So we pass the receiver by reference
// so that if the call to std::invoke() throws that we can catch the
// exception and pass it into 'receiver'.
#if FOLLY_HAS_EXCEPTIONS
try
{
#endif
// Create a lambda coroutine and immediately invoke it:
[](A a, R&& r) -> oneway_task
{
// Receivers should be nothrow move-constructible so we should't need to
// worry about this potentially throwing.
R rCopy(static_cast<R&&>(r));
#if FOLLY_HAS_EXCEPTIONS
try
{
#endif
PUSHMI_IF_CONSTEXPR ((std::is_void<coro::await_result_t<A>>::value) (
co_await static_cast<A&&>(a);
set_value(id(rCopy));
) else (
set_value(id(rCopy), co_await static_cast<A&&>(a));
))
set_done(rCopy);
#if FOLLY_HAS_EXCEPTIONS
}
catch (...)
{
set_error(rCopy, std::current_exception());
}
#endif
}(static_cast<A&&>(awaitable), static_cast<R&&>(to));
#if FOLLY_HAS_EXCEPTIONS
}
catch (...)
{
set_error(to, std::current_exception());
}
#endif
}
#endif
} // namespace _submit_adl
FOLLY_DEFINE_CPO(_submit_adl::_fn, submit)
} // namespace pushmi
} // namespace folly
......@@ -27,10 +27,10 @@ namespace pushmi {
// Traits types:
template< class, class = void >
template <class T, class = void>
struct sender_traits;
template< class, class = void >
template <class T, class = void>
struct receiver_traits;
// implementation types
......@@ -126,5 +126,12 @@ struct any {
};
} // namespace detail
namespace awaitable_senders {
// Used in the definition of sender_traits to define Senders in terms
// Awaitables without causing constraint recursion.
std::false_type safe_to_test_awaitable(void*);
struct sender_adl_hook;
} // namespace awaitable_senders
} // namespace pushmi
} // namespace folly
/*
* Copyright 2018-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if defined(__cpp_coroutines)
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/detail/InlineTask.h>
#include <folly/experimental/pushmi/single_sender.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace folly::pushmi::aliases;
using namespace testing;
namespace {
struct answer : mi::single_sender_tag::with_values<int> {
PUSHMI_TEMPLATE(class To)( //
requires mi::ReceiveValue<To&, int> //
) //
void submit(To to) {
mi::set_value(to, 42);
mi::set_done(to);
}
};
PUSHMI_TEMPLATE(class From)( //
requires mi::SingleTypedSender<From> //
) //
auto awaitSender(From from) ->
folly::coro::detail::InlineTask<
mi::sender_values_t<From, mi::detail::identity_t>> {
co_return co_await from;
}
folly::coro::detail::InlineTask<int> fetchAnswer() {
co_return 42;
}
} // <anonymous> namespace
TEST(CoroTest, AwaitSender) {
int ans = folly::coro::blockingWait(::awaitSender(answer{}));
EXPECT_THAT(ans, Eq(42)) << "Expected the answer to be 42";
}
TEST(CoroTest, SubmitAwaitable) {
int ans = 0;
mi::submit(
fetchAnswer(),
mi::make_receiver([&](int j){ ans = j; }));
EXPECT_THAT(ans, Eq(42)) << "Expected the answer to be 42";
}
#endif
......@@ -16,7 +16,6 @@
#pragma once
#include <type_traits>
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/experimental/pushmi/detail/concept_def.h>
......@@ -228,19 +227,25 @@ struct Enable_<true> {
};
template <class...>
struct Front_ {};
struct FrontOrVoid_ {
using type = void;
};
template <class T, class... Us>
struct Front_<T, Us...> {
struct FrontOrVoid_<T, Us...> {
using type = T;
};
// An alias for the type in the pack if the pack has exactly one type in it.
// Otherwise, this SFINAE's away. T cannot be an array type of an abstract type.
// Instantiation proceeds from left to right. The use of Enable_ here avoids
// needless instantiations of Front_.
// needless instantiations of FrontOrVoid_.
template<class...Ts>
using identity_t = typename Enable_<sizeof...(Ts) == 1u>::
template _type<Front_<Ts...>>::type;
template _type<FrontOrVoid_<Ts...>>::type;
template<class...Ts>
using identity_or_void_t = typename Enable_<sizeof...(Ts) <= 1u>::
template _type<FrontOrVoid_<Ts...>>::type;
} // namespace detail
} // namespace pushmi
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment