Commit 3336c6ba authored by Lewis Baker's avatar Lewis Baker Committed by Facebook Github Bot

Add coroutine traits metafunctions to folly::coro

Summary:
Adds four template metafunctions that can be used when building generic coroutine code.

- `is_awaitable<T>`, `is_awaitable_v<T>`
  Query whether a type can be co_awaited within a "normal" coroutine.
- `is_awaiter<T>`, `is_awaiter_v<T>`
  Query whether a type implements the `await_ready()`, `await_suspend()` and `await_resume()` methods required for the 'Awaiter' concept.
- `awaiter_type<T>`, `awaiter_type_t<T>`
  Query what the 'Awaiter' type is for a given 'Awaitable' type. This will be the return-type of whatever `operator co_await()` returns, if one is defined, otherwise is type of the Awaitable itself.
- `await_result<T>`, `await_result_t<T>`
  Query what the type resulting from applying the `co_await` operator to a value of type, `T`.
- Moved the existing `folly::coro::get_awaiter()` implementation from 'AwaitWrapper.h' into 'Traits.h'.

Note that these traits all currently assume that the `co_await` operator appears within a coroutine that does not provide an `await_transform()` customisation point. A promise type that implements the `await_transform()` customisation point can arbitrarily change the behaviour of a `co_await` that occurs within its body.

Reviewed By: andriigrynenko

Differential Revision: D9382593

fbshipit-source-id: e9ddfec4a62579a4b2649c070e53e8639e7c68a1
parent 72e396ca
......@@ -22,6 +22,7 @@
#include <folly/ExceptionString.h>
#include <folly/Executor.h>
#include <folly/Optional.h>
#include <folly/experimental/coro/Traits.h>
namespace folly {
namespace coro {
......@@ -133,43 +134,11 @@ class AwaitWrapper {
Optional<Awaitable> awaitable_;
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++17-extensions"
FOLLY_CREATE_MEMBER_INVOKE_TRAITS(
member_operator_co_await_traits,
operator co_await);
template <typename Awaitable>
inline constexpr bool has_member_operator_co_await_v =
member_operator_co_await_traits::is_invocable<Awaitable>::value;
FOLLY_CREATE_FREE_INVOKE_TRAITS(
non_member_operator_co_await_traits,
operator co_await);
template <typename Awaitable>
inline constexpr bool has_non_member_operator_co_await_v =
non_member_operator_co_await_traits::is_invocable<Awaitable>::value;
} // namespace detail
template <typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable) {
if constexpr (detail::has_member_operator_co_await_v<Awaitable&&>) {
return std::forward<Awaitable>(awaitable).operator co_await();
} else if constexpr (detail::has_non_member_operator_co_await_v<
Awaitable&&>) {
return operator co_await(std::forward<Awaitable>(awaitable));
} else {
// This is necessary for it to work with std::reference_wrapper
return static_cast<Awaitable&>(awaitable);
}
}
template <typename Awaitable>
auto createAwaitWrapper(Awaitable&& awaitable) {
using Awaiter =
decltype(::folly::coro::get_awaiter(std::declval<Awaitable&&>()));
using Awaiter = folly::coro::awaiter_type_t<Awaitable>;
using Wrapper = std::conditional_t<
std::is_reference<Awaiter>::value,
std::reference_wrapper<std::remove_reference_t<Awaiter>>,
......@@ -180,8 +149,7 @@ auto createAwaitWrapper(Awaitable&& awaitable) {
template <typename Awaitable>
auto createAwaitWrapper(Awaitable&& awaitable, folly::Executor* executor) {
using Awaiter =
decltype(::folly::coro::get_awaiter(std::declval<Awaitable&&>()));
using Awaiter = folly::coro::awaiter_type_t<Awaitable>;
using Wrapper = std::conditional_t<
std::is_reference<Awaiter>::value,
std::reference_wrapper<std::remove_reference_t<Awaiter>>,
......@@ -190,7 +158,5 @@ auto createAwaitWrapper(Awaitable&& awaitable, folly::Executor* executor) {
::folly::coro::get_awaiter(std::forward<Awaitable>(awaitable)), executor);
}
#pragma clang diagnostic pop
} // namespace coro
} // 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.
*/
#pragma once
#include <folly/Traits.h>
#include <experimental/coroutine>
#include <type_traits>
namespace folly {
namespace coro {
namespace detail {
template <typename T>
struct _is_coroutine_handle : std::false_type {};
template <typename T>
struct _is_coroutine_handle<std::experimental::coroutine_handle<T>>
: std::true_type {};
template <typename T>
struct _is_valid_await_suspend_return_type : folly::Disjunction<
std::is_void<T>,
std::is_same<bool, T>,
_is_coroutine_handle<T>> {};
} // namespace detail
/// is_awaiter<T>::value
/// is_awaiter_v<T>
///
/// Template metafunction for querying whether the specified type implements
/// the 'Awaiter' concept.
///
/// An 'Awaiter' must have the following three methods.
/// - awaiter.await_ready() -> bool
/// - awaiter.await_suspend(std::experimental::coroutine_handle<void>()) ->
/// void OR
/// bool OR
/// std::experimental::coroutine_handle<T> for some T
/// - awaiter.await_resume()
///
/// Note that the requirement to accept coroutine_handle<void> rather than
/// just some coroutine_handle<P> is to ensure that the awaitable can be
/// awaited in any coroutine context where the promise_type does not modify
/// what is normally awaitable through use of await_transform().
template <typename T, typename = void>
struct is_awaiter : std::false_type {};
template <typename T>
struct is_awaiter<
T,
folly::void_t<
decltype(std::declval<T&>().await_ready()),
decltype(std::declval<T&>().await_suspend(
std::declval<std::experimental::coroutine_handle<void>>())),
decltype(std::declval<T&>().await_resume())>>
: folly::Conjunction<
std::is_same<bool, decltype(std::declval<T&>().await_ready())>,
detail::_is_valid_await_suspend_return_type<decltype(
std::declval<T&>().await_suspend(
std::declval<
std::experimental::coroutine_handle<void>>()))>> {};
template <typename T>
constexpr bool is_awaiter_v = is_awaiter<T>::value;
namespace detail {
template <typename Awaitable, typename = void>
struct _has_member_operator_co_await : std::false_type {};
template <typename Awaitable>
struct _has_member_operator_co_await<
Awaitable,
folly::void_t<decltype(std::declval<Awaitable>().operator co_await())>>
: is_awaiter<decltype(std::declval<Awaitable>().operator co_await())> {};
template <typename Awaitable, typename = void>
struct _has_free_operator_co_await : std::false_type {};
template <typename Awaitable>
struct _has_free_operator_co_await<
Awaitable,
folly::void_t<decltype(operator co_await(std::declval<Awaitable>()))>>
: is_awaiter<decltype(operator co_await(std::declval<Awaitable>()))> {};
} // namespace detail
/// is_awaitable<T>::value
/// is_awaitable_v<T>
///
/// Query if a type, T, is awaitable within the context of any coroutine whose
/// promise_type does not have an await_transform() that modifies what is
/// normally awaitable.
///
/// A type, T, is awaitable if it is an Awaiter, or if it has either a
/// member operator co_await() or a free-function operator co_await() that
/// returns an Awaiter.
template <typename T>
struct is_awaitable : folly::Disjunction<
detail::_has_member_operator_co_await<T>,
detail::_has_free_operator_co_await<T>,
is_awaiter<T>> {};
template <typename T>
constexpr bool is_awaitable_v = is_awaitable<T>::value;
/// get_awaiter(Awaitable&&) -> awaiter_type_t<Awaitable>
///
/// The get_awaiter() function takes an Awaitable type and returns a value
/// that contains the await_ready(), await_suspend() and await_resume() methods
/// for that type.
///
/// This encapsulates calling 'operator co_await()' if it exists.
template <
typename Awaitable,
std::enable_if_t<
folly::Conjunction<
is_awaiter<Awaitable>,
folly::Negation<detail::_has_free_operator_co_await<Awaitable>>,
folly::Negation<detail::_has_member_operator_co_await<Awaitable>>>::
value,
int> = 0>
Awaitable& get_awaiter(Awaitable&& awaitable) {
return awaitable;
}
template <
typename Awaitable,
std::enable_if_t<
detail::_has_member_operator_co_await<Awaitable>::value,
int> = 0>
decltype(auto) get_awaiter(Awaitable&& awaitable) {
return static_cast<Awaitable&&>(awaitable).operator co_await();
}
template <
typename Awaitable,
std::enable_if_t<
folly::Conjunction<
detail::_has_free_operator_co_await<Awaitable>,
folly::Negation<detail::_has_member_operator_co_await<Awaitable>>>::
value,
int> = 0>
decltype(auto) get_awaiter(Awaitable&& awaitable) {
return operator co_await(static_cast<Awaitable&&>(awaitable));
}
/// awaiter_type<Awaitable>
///
/// A template-metafunction that lets you query the type that will be used
/// as the Awaiter object when you co_await a value of type Awaitable.
/// This is the return-type of get_awaiter() when passed a value of type
/// Awaitable.
template <typename Awaitable, typename = void>
struct awaiter_type {};
template <typename Awaitable>
struct awaiter_type<Awaitable, std::enable_if_t<is_awaitable_v<Awaitable>>> {
using type = decltype(get_awaiter(std::declval<Awaitable>()));
};
/// await_result<Awaitable>
///
/// A template metafunction that allows you to query the type that will result
/// from co_awaiting a value of that type in the context of a coroutine that
/// does not modify the normal behaviour with promise_type::await_transform().
template <typename Awaitable>
using awaiter_type_t = typename awaiter_type<Awaitable>::type;
template <typename Awaitable, typename = void>
struct await_result {};
template <typename Awaitable>
struct await_result<Awaitable, std::enable_if_t<is_awaitable_v<Awaitable>>> {
using type = decltype(get_awaiter(std::declval<Awaitable>()).await_resume());
};
template <typename Awaitable>
using await_result_t = typename await_result<Awaitable>::type;
} // namespace coro
} // 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.
*/
#include <folly/Portability.h>
#if FOLLY_HAS_COROUTINES
#include <folly/experimental/coro/Traits.h>
#include <experimental/coroutine>
#include <type_traits>
using namespace folly::coro;
template <typename T>
struct SomeAwaiter1 {
bool await_ready();
void await_suspend(std::experimental::coroutine_handle<>);
T await_resume();
};
template <typename T>
struct SomeAwaiter2 {
bool await_ready();
bool await_suspend(std::experimental::coroutine_handle<>);
T await_resume();
};
template <typename T>
struct SomeAwaiter3 {
bool await_ready();
std::experimental::coroutine_handle<> await_suspend(
std::experimental::coroutine_handle<>);
T await_resume();
};
struct MissingAwaitReady {
void await_suspend(std::experimental::coroutine_handle<>);
int await_resume();
};
struct WrongAwaitReadyReturnType {
void* await_ready();
void await_suspend(std::experimental::coroutine_handle<>);
int await_resume();
};
struct MissingAwaitSuspend {
bool await_ready();
int await_resume();
};
struct WrongAwaitSuspendArgType {
bool await_ready();
void await_suspend(float);
int await_resume();
};
struct MissingAwaitResume {
bool await_ready();
void await_suspend(std::experimental::coroutine_handle<void>);
};
struct MemberOperatorCoAwait {
SomeAwaiter1<void> operator co_await() &;
SomeAwaiter2<int> operator co_await() &&;
SomeAwaiter3<float> operator co_await() const&;
};
struct FreeOperatorCoAwait {};
SomeAwaiter1<void> operator co_await(FreeOperatorCoAwait);
struct MoveOnlyFreeOperatorCoAwait {};
SomeAwaiter1<int> operator co_await(MoveOnlyFreeOperatorCoAwait&&);
struct MemberOperatorCoAwaitWithInvalidAwaiter {
int operator co_await();
};
static_assert(is_awaiter_v<SomeAwaiter1<void>>, "");
static_assert(is_awaiter_v<SomeAwaiter2<int>>, "");
static_assert(is_awaiter_v<SomeAwaiter3<float>>, "");
static_assert(!is_awaiter_v<void>, "");
static_assert(!is_awaiter_v<int>, "");
static_assert(!is_awaiter_v<MissingAwaitReady>, "");
static_assert(!is_awaiter_v<WrongAwaitReadyReturnType>, "");
static_assert(!is_awaiter_v<MissingAwaitSuspend>, "");
static_assert(!is_awaiter_v<WrongAwaitSuspendArgType>, "");
static_assert(!is_awaiter_v<MissingAwaitResume>, "");
static_assert(!is_awaiter_v<MemberOperatorCoAwait>, "");
static_assert(is_awaitable_v<SomeAwaiter1<void>>, "");
static_assert(is_awaitable_v<SomeAwaiter2<int>>, "");
static_assert(is_awaitable_v<SomeAwaiter3<void*>>, "");
static_assert(is_awaitable_v<MemberOperatorCoAwait>, "");
static_assert(is_awaitable_v<MemberOperatorCoAwait&>, "");
static_assert(is_awaitable_v<MemberOperatorCoAwait&&>, "");
static_assert(is_awaitable_v<const MemberOperatorCoAwait&>, "");
static_assert(is_awaitable_v<FreeOperatorCoAwait>, "");
static_assert(is_awaitable_v<FreeOperatorCoAwait&&>, "");
static_assert(is_awaitable_v<const FreeOperatorCoAwait&>, "");
static_assert(is_awaitable_v<MoveOnlyFreeOperatorCoAwait&&>, "");
static_assert(!is_awaitable_v<MoveOnlyFreeOperatorCoAwait&>, "");
static_assert(!is_awaitable_v<const MoveOnlyFreeOperatorCoAwait&>, "");
static_assert(!is_awaitable_v<void>, "");
static_assert(!is_awaitable_v<MemberOperatorCoAwaitWithInvalidAwaiter>, "");
static_assert(
std::is_same<awaiter_type_t<SomeAwaiter1<void>>, SomeAwaiter1<void>&>::
value,
"");
static_assert(
std::is_same<awaiter_type_t<MemberOperatorCoAwait>, SomeAwaiter2<int>>::
value,
"");
static_assert(
std::is_same<awaiter_type_t<MemberOperatorCoAwait&>, SomeAwaiter1<void>>::
value,
"");
static_assert(
std::is_same<awaiter_type_t<FreeOperatorCoAwait>, SomeAwaiter1<void>>::
value,
"");
static_assert(
std::is_same<await_result_t<SomeAwaiter1<void>>, void>::value,
"");
static_assert(
std::is_same<await_result_t<MemberOperatorCoAwait>, int>::value,
"");
static_assert(
std::is_same<await_result_t<MemberOperatorCoAwait&>, void>::value,
"");
static_assert(
std::is_same<await_result_t<const MemberOperatorCoAwait&>, float>::value,
"");
static_assert(
std::is_same<await_result_t<MoveOnlyFreeOperatorCoAwait>, int>::value,
"");
#endif
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