Commit da78383f authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

co_invoke

Summary:
[Folly] `folly::coro::co_invoke`, both generalizing and constraining `folly::coro::lambda` and modeling on `std::invoke`.

Constrained only to work on callables which return `Task<_>` in order to be sure that `invoke_result_t<F, A...>` is the same as the type of `co_await <expr>` where `expr` has type `invoke_result_t<F, A...>`. We know that this constraint holds for `Task<_>`. The alternative is to make it work for all types and use `decltype(auto)` as the return type.

Reviewed By: andriigrynenko

Differential Revision: D13523334

fbshipit-source-id: 9af220dd45d6b9f6676c5ef49ba2e01395babd72
parent dd52ab90
...@@ -368,5 +368,45 @@ inline Task<void> detail::TaskPromise<void>::get_return_object() noexcept { ...@@ -368,5 +368,45 @@ inline Task<void> detail::TaskPromise<void>::get_return_object() noexcept {
detail::TaskPromise<void>>::from_promise(*this)}; detail::TaskPromise<void>>::from_promise(*this)};
} }
namespace detail {
template <typename T>
struct is_task : std::false_type {};
template <typename T>
struct is_task<Task<T>> : std::true_type {};
template <typename F, typename... A, typename F_, typename... A_>
invoke_result_t<F, A...> co_invoke_(F_ f, A_... a) {
co_return co_await folly::invoke(static_cast<F&&>(f), static_cast<A&&>(a)...);
}
} // namespace detail
/// co_invoke
///
/// This utility function is a safe way to instantiate a coroutine using a
/// coroutine callable. It guarantees that the callable and the arguments
/// outlive the coroutine which invocation returns. Otherwise, the callable
/// and the arguments are not safe to be used within the coroutine body.
///
/// For example, if the callable is a lambda with captures, the captures would
/// not otherwise be safe to use in the coroutine body without using co_invoke.
///
/// Models std::invoke for any callable object which returns Task<_>.
///
/// Like std::invoke in that the callable is invoked with the cvref-qual with
/// which it is passed to co_invoke and the arguments are passed with the cvref-
/// quals with which they were passed to co_invoke.
///
/// Different from std::invoke in that the callable and all arguments are decay-
/// copied and held for the lifetime of the coroutine, whereas std::invoke never
/// never constructs anything from the callable or the arguments.
template <typename F, typename... A>
std::enable_if_t<
detail::is_task<invoke_result_t<F, A...>>::value,
invoke_result_t<F, A...>>
co_invoke(F&& f, A&&... a) {
return detail::co_invoke_<F, A...>(
static_cast<F&&>(f), static_cast<A&&>(a)...);
}
} // namespace coro } // namespace coro
} // namespace folly } // namespace folly
...@@ -155,14 +155,5 @@ TimedWaitAwaitable<std::decay_t<Awaitable>> timed_wait( ...@@ -155,14 +155,5 @@ TimedWaitAwaitable<std::decay_t<Awaitable>> timed_wait(
std::forward<Awaitable>(awaitable), duration); std::forward<Awaitable>(awaitable), duration);
} }
/// This helper method is a safe way to instantiate a coroutine using a
/// coroutine lambda. It provides a guarantee that the lambda will always
/// outlive the coroutine which it returned.
/// Otherwise, using any lambda captures in the coroutine body is not safe.
template <typename Lambda>
std::result_of_t<Lambda()> lambda(Lambda f) {
co_return co_await f();
}
} // namespace coro } // namespace coro
} // namespace folly } // namespace folly
...@@ -362,11 +362,11 @@ TEST(Coro, MoveOnlyReturn) { ...@@ -362,11 +362,11 @@ TEST(Coro, MoveOnlyReturn) {
EXPECT_EQ(42, *value); EXPECT_EQ(42, *value);
} }
TEST(Coro, lambda) { TEST(Coro, co_invoke) {
ManualExecutor executor; ManualExecutor executor;
Promise<folly::Unit> p; Promise<folly::Unit> p;
auto coroFuture = auto coroFuture =
coro::lambda([f = p.getSemiFuture()]() mutable -> coro::Task<void> { coro::co_invoke([f = p.getSemiFuture()]() mutable -> coro::Task<void> {
(void)co_await std::move(f); (void)co_await std::move(f);
co_return; co_return;
}) })
...@@ -400,7 +400,7 @@ TEST(Coro, Semaphore) { ...@@ -400,7 +400,7 @@ TEST(Coro, Semaphore) {
&evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); }); &evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); });
for (size_t i = 0; i < kTasks; ++i) { for (size_t i = 0; i < kTasks; ++i) {
coro::lambda([&, completionCounter]() -> coro::Task<void> { coro::co_invoke([&, completionCounter]() -> coro::Task<void> {
for (size_t j = 0; j < kIterations; ++j) { for (size_t j = 0; j < kIterations; ++j) {
co_await sem.co_wait(); co_await sem.co_wait();
++counter; ++counter;
......
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