From be6571091b67bd70716a80cd569e83e5dcf9be0e Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Wed, 14 Mar 2018 09:21:32 +0100 Subject: [PATCH] Implement continuables as return types for coroutines * Closes #4 --- doc/tutorial-awaiting-continuables.dox | 24 +++- include/continuable/continuable-base.hpp | 25 ++++- include/continuable/continuable-coroutine.hpp | 66 +++++++++++ .../continuable/continuable-promise-base.hpp | 5 + include/continuable/continuable.hpp | 1 + include/continuable/detail/awaiting.hpp | 102 ++++++++++++++--- include/continuable/detail/types.hpp | 2 + .../multi/test-continuable-await.cpp | 103 ++++++++---------- 8 files changed, 245 insertions(+), 83 deletions(-) create mode 100644 include/continuable/continuable-coroutine.hpp diff --git a/doc/tutorial-awaiting-continuables.dox b/doc/tutorial-awaiting-continuables.dox index 13688b5..45dafc8 100644 --- a/doc/tutorial-awaiting-continuables.dox +++ b/doc/tutorial-awaiting-continuables.dox @@ -96,16 +96,34 @@ result.get_value(); result.get_exception(); \endcode +\section tutorial-awaiting-continuables-return Using continuables as return type from coroutines + +It is possible to use a \ref continuable_base as return type from coroutines. -\note It isn't possible as of now to use a \ref continuable_base - as return type from coroutines: \code{.cpp} -cti::continuable do_sth() { +cti::continuable<> resolve_async_void() { + co_await http_request("github.com"); + // ... + co_return; +} + +cti::continuable resolve_async() { co_await http_request("github.com"); // ... co_return 0; } \endcode +Additionally it's possible to return multiple return values from coroutines +by wrapping those in a tuple like type: + +\code{.cpp} +cti::continuable resolve_async_multiple() { + co_await http_request("github.com"); + // ... + co_return std::make_tuple(0, 1, 2); +} +\endcode + */ } diff --git a/include/continuable/continuable-base.hpp b/include/continuable/continuable-base.hpp index f8a0229..4914e86 100644 --- a/include/continuable/continuable-base.hpp +++ b/include/continuable/continuable-base.hpp @@ -48,6 +48,7 @@ #ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE #include +#include #endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE namespace cti { @@ -670,16 +671,32 @@ public: /// result.get_exception(); /// ``` /// - /// \attention Note that it isn't possible as of now to use a continuable - /// as return type from coroutines as depicted below: + /// \note Using continuable_base as return type for coroutines + /// is supported. The coroutine is initially stopped and + /// resumed when the continuation is requested in order to + /// keep the lazy evaluation semantics of the continuable_base. /// ```cpp - /// cti::continuable do_sth() { + /// cti::continuable<> resolve_async_void() { + /// co_await http_request("github.com"); + /// // ... + /// co_return; + /// } + /// + /// cti::continuable resolve_async() { /// co_await http_request("github.com"); /// // ... /// co_return 0; /// } /// ``` - /// Propably this will be added in a future version of the library. + /// It's possible to return multiple return values from coroutines + /// by wrapping those in a tuple like type: + /// ```cpp + /// cti::continuable resolve_async_multiple() { + /// co_await http_request("github.com"); + /// // ... + /// co_return std::make_tuple(0, 1, 2); + /// } + /// ``` /// /// \since 2.0.0 auto operator co_await() && { diff --git a/include/continuable/continuable-coroutine.hpp b/include/continuable/continuable-coroutine.hpp new file mode 100644 index 0000000..a6be454 --- /dev/null +++ b/include/continuable/continuable-coroutine.hpp @@ -0,0 +1,66 @@ +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v3.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_COROUTINE_HPP_INCLUDED +#define CONTINUABLE_COROUTINE_HPP_INCLUDED + +#include +#include +#include +#include + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +#include +#endif // CONTINUABLE_HAS_EXCEPTIONS + +#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE +#include +#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE + +/// \cond false +#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE +// As far as I know there is no other way to implement this specialization... +// NOLINTNEXTLINE(cert-dcl58-cpp) +namespace std { +namespace experimental { +template +struct coroutine_traits< + cti::continuable_base>, + FunctionArgs...> { + + using promise_type = + cti::detail::awaiting::promise_type, Args...>; +}; +} // namespace experimental +} // namespace std +#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE +/// \endcond + +#endif // CONTINUABLE_COROUTINE_HPP_INCLUDED diff --git a/include/continuable/continuable-promise-base.hpp b/include/continuable/continuable-promise-base.hpp index a007fbc..cada7ef 100644 --- a/include/continuable/continuable-promise-base.hpp +++ b/include/continuable/continuable-promise-base.hpp @@ -76,6 +76,11 @@ public: /// Constructor accepting the data object explicit promise_base(Data data) : data_(std::move(data)) { } + /// \cond false + /// Constructor for constructing an empty promise + explicit promise_base(detail::types::promise_no_init_tag) { + } + /// \endcond /// Constructor accepting any object convertible to the data object template #include +#include #include #include #include diff --git a/include/continuable/detail/awaiting.hpp b/include/continuable/detail/awaiting.hpp index 34c2488..0742db4 100644 --- a/include/continuable/detail/awaiting.hpp +++ b/include/continuable/detail/awaiting.hpp @@ -34,10 +34,12 @@ #include #include +#include #include #include #include +#include #include #include @@ -120,25 +122,91 @@ template constexpr auto create_awaiter(T&& continuable) { return awaitable>(std::forward(continuable)); } + +/// This makes it possible to take the coroutine_handle over on suspension +struct handle_takeover { + coroutine_handle<>& handle_; + + bool await_ready() noexcept { + return false; + } + + void await_suspend(coroutine_handle<> handle) noexcept { + handle_ = handle; + } + + void await_resume() noexcept { + } +}; + +/// The type which is passed to the compiler that describes the properties +/// of a continuable_base used as coroutine promise type. +template +struct promise_type; + +/// Implements the resolving method return_void and return_value accordingly +template +struct promise_resolver_base; + +template +struct promise_resolver_base> { + void return_void() { + auto me = static_cast*>(this); + me->promise_.set_value(); + } +}; +template +struct promise_resolver_base> { + void return_value(T value) { + auto me = static_cast*>(this); + me->promise_.set_value(std::move(value)); + } +}; +template +struct promise_resolver_base> { + template + void return_value(T&& tuple_like) { + auto me = static_cast*>(this); + traits::unpack(std::move(me->promise_), std::forward(tuple_like)); + } +}; + +template +struct promise_type + : promise_resolver_base> { + + coroutine_handle<> handle_; + Promise promise_; + + explicit promise_type() : promise_(types::promise_no_init_tag{}) { + } + + auto get_return_object() { + return [this](auto&& promise) { + promise_ = std::forward(promise); + handle_.resume(); + }; + } + + handle_takeover initial_suspend() { + return {handle_}; + } + + std::experimental::suspend_never final_suspend() { + return {}; + } + + void unhandled_exception() noexcept { +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + promise_.set_exception(std::current_exception()); +#else // CONTINUABLE_HAS_EXCEPTIONS + // Returning error types from coroutines isn't supported + cti::detail::util::trap(); +#endif // CONTINUABLE_HAS_EXCEPTIONS + } +}; } // namespace awaiting } // namespace detail } // namespace cti -// As far as I know there is no other was to implement this specialization... -// NOLINTNEXTLINE(cert-dcl58-cpp) -namespace std { -namespace experimental { -template -struct coroutine_traits< - cti::continuable_base>, - FunctionArgs...> { - - static_assert(cti::detail::traits::fail::value, - "Using a continuable as return type from co_return " - "expressions isn't supported yet!"); -}; -} // namespace experimental -} // namespace std - #endif // CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED diff --git a/include/continuable/detail/types.hpp b/include/continuable/detail/types.hpp index 97bedfa..795ef56 100644 --- a/include/continuable/detail/types.hpp +++ b/include/continuable/detail/types.hpp @@ -77,6 +77,8 @@ public: using T::operator(); }; +/// Tag for constructing an empty promise_base . +struct promise_no_init_tag {}; } // namespace types } // namespace detail } // namespace cti diff --git a/test/unit-test/multi/test-continuable-await.cpp b/test/unit-test/multi/test-continuable-await.cpp index 8bef212..460ed49 100644 --- a/test/unit-test/multi/test-continuable-await.cpp +++ b/test/unit-test/multi/test-continuable-await.cpp @@ -32,42 +32,9 @@ #include -namespace std { -namespace experimental { -template -struct coroutine_traits { - struct promise_type { - void get_return_object() { - } - - void set_exception(exception_ptr const&) noexcept { - } - - // FIXME This throws errors in MSVC but is required in clang -#ifndef _MSC_VER - void unhandled_exception() { - GTEST_FATAL_FAILURE_("Unhandled async exception!"); - } -#endif // _MSC_VER - - suspend_never initial_suspend() noexcept { - return {}; - } - - suspend_never final_suspend() noexcept { - return {}; - } - - void return_void() noexcept { - } - }; -}; -} // namespace experimental -} // namespace std - /// Resolves the given promise asynchonously -template -void resolve_async(S&& supplier, T promise) { +template +cti::continuable<> resolve_async(S&& supplier) { // 0 args co_await supplier(); @@ -79,21 +46,34 @@ void resolve_async(S&& supplier, T promise) { std::tuple a2 = co_await supplier(1, 2); EXPECT_EQ(a2, std::make_tuple(1, 2)); - promise.set_value(); co_return; } +template +cti::continuable resolve_async_one(S&& supplier) { + // Pseudo wait + co_await supplier(); + + co_return 4644; +} + +template +cti::continuable resolve_async_multiple(S&& supplier) { + // Pseudo wait + co_await supplier(); + + co_return std::make_tuple(0, 1, 2, 3); +} + TYPED_TEST(single_dimension_tests, are_awaitable) { auto const& supply = [&](auto&&... args) { // Supplies the current tested continuable return this->supply(std::forward(args)...); }; - EXPECT_ASYNC_RESULT( - this->supply().then(cti::make_continuable([&](auto promise) { - // Resolve the cotinuable through a coroutine - resolve_async(supply, std::move(promise)); - }))); + EXPECT_ASYNC_RESULT(resolve_async(supply)); + EXPECT_ASYNC_RESULT(resolve_async_one(supply), 4644); + EXPECT_ASYNC_RESULT(resolve_async_multiple(supply), 0, 1, 2, 3); } #ifndef CONTINUABLE_WITH_NO_EXCEPTIONS @@ -109,8 +89,8 @@ struct await_exception : std::exception { }; /// Resolves the given promise asynchonously through an exception -template -void resolve_async_exceptional(S&& supplier, T promise) { +template +cti::continuable<> resolve_async_exceptional(S&& supplier) { // 0 args co_await supplier(); @@ -128,7 +108,6 @@ void resolve_async_exceptional(S&& supplier, T promise) { EXPECT_THROW(co_await supplier().then([] { throw await_exception{}; }), await_exception); - promise.set_value(); co_return; } @@ -138,23 +117,29 @@ TYPED_TEST(single_dimension_tests, are_awaitable_with_exceptions) { return this->supply(std::forward(args)...); }; - ASSERT_ASYNC_COMPLETION( - this->supply().then(cti::make_continuable([&](auto promise) { - // Resolve the cotinuable through a coroutine - resolve_async_exceptional(supply, std::move(promise)); - }))); + ASSERT_ASYNC_COMPLETION(resolve_async_exceptional(supply)); } -// TODO Implement this later -// -// static cti::continuable async_await() { -// co_await cti::make_continuable([](auto&& promise) { -// // ... -// promise.set_value(); -// }); -// -// co_return 1; -// } +/// Resolves the given promise asynchonously through an exception +template +cti::continuable<> resolve_coro_exceptional(S&& supplier) { + // Pseudo wait + co_await supplier(); + + throw await_exception{}; + + co_return; +} + +TYPED_TEST(single_dimension_tests, are_awaitable_with_exceptions_from_coro) { + auto const& supply = [&](auto&&... args) { + // Supplies the current tested continuable + return this->supply(std::forward(args)...); + }; + + ASSERT_ASYNC_EXCEPTION_RESULT(resolve_coro_exceptional(supply), + await_exception{}) +} #endif // CONTINUABLE_WITH_NO_EXCEPTIONS