diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h index 92a3cfbe43..a4444a6f65 100644 --- a/include/dpp/coro/async.h +++ b/include/dpp/coro/async.h @@ -53,22 +53,30 @@ namespace async { */ template struct callback { + /** + * @brief Promise object to set the result into + */ std::shared_ptr> promise{nullptr}; - void operator()(const R& v) const { + /** + * @brief Call operator, sets the value in the promise and notifies any awaiter + */ + void operator()(const detail::argument& v) const requires (detail::is_copy_constructible) { promise->set_value(v); } - - void operator()(R&& v) const { + + /** + * @brief Call operator, sets the value in the promise and notifies any awaiter + */ + void operator()(detail::argument&& v) const requires (detail::is_move_constructible) { promise->set_value(std::move(v)); } -}; - -template <> -struct callback { - std::shared_ptr> promise{nullptr}; - - void operator()() const { + + /** + * @brief Call operator, sets the value in the promise and notifies any awaiter + */ + void operator()() const requires (std::is_void_v) + { promise->set_value(); } }; @@ -91,9 +99,14 @@ struct confirmation_callback_t; */ template class async : public awaitable { - + /** + * @brief Callable object to pass to API calls + */ detail::async::callback api_callback{}; + /** + * @brief Internal promise constructor, grabs a promise object for the callback to use + */ explicit async(std::shared_ptr> &&promise) : awaitable{promise.get()}, api_callback{std::move(promise)} {} public: @@ -135,12 +148,29 @@ class async : public awaitable { std::invoke(std::forward(fun), std::forward(args)..., api_callback); } + /** + * @brief Copy constructor is disabled. + */ async(const async&) = delete; - async(async&&) = default; + /** + * @brief Move constructor, moves the awaitable async object + */ + async(async&&) = default; + + /** + * @brief Copy assignment operator is disabled. + */ async& operator=(const async&) = delete; + + /** + * @brief Move assignment operator, moves the awaitable async object + */ async& operator=(async&&) = default; + /** + * @brief Destructor, signals to the callback that the async object is gone and shouldn't be notified of the result + */ ~async() { this->abandon(); } diff --git a/include/dpp/coro/awaitable.h b/include/dpp/coro/awaitable.h index 9ae78353ee..16fc58b70a 100644 --- a/include/dpp/coro/awaitable.h +++ b/include/dpp/coro/awaitable.h @@ -123,6 +123,9 @@ class basic_awaitable { detail::promise::spawn_sync_wait_job(static_cast(this), cv, result); do_wait(cv, result); + /* + * Note: we use .index() here to support dpp::promise & dpp::promise :D + */ if (result.index() == 2) { std::rethrow_exception(std::get<2>(result)); } @@ -365,12 +368,14 @@ class promise_base { /** * @brief Variant representing one of either 3 states of the result value : empty, result, exception. */ - using storage_type = std::variant, empty, T>, std::exception_ptr>; + using storage_type = result_t; /** * @brief State of the result value. * * @see storage_type + * + * @note use .index() instead of std::holds_alternative to support promise_base and promise_base :) */ storage_type value = std::monostate{}; @@ -526,7 +531,8 @@ class basic_promise : public detail::promise::promise_base { * @throws dpp::logic_exception if the promise is not empty. */ template - void set_value(const T& v) requires (std::copy_constructible) { + requires (detail::is_copy_constructible) + void set_value(const detail::argument& v) { emplace_value(v); } @@ -537,35 +543,22 @@ class basic_promise : public detail::promise::promise_base { * @throws dpp::logic_exception if the promise is not empty. */ template - void set_value(T&& v) requires (std::move_constructible) { + requires (detail::is_move_constructible) + void set_value(detail::argument&& v) { emplace_value(std::move(v)); } -}; - - -/** - * @brief Generic promise class, represents the owning potion of an asynchronous value. - * - * This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state. - * As such, the promise needs to be kept alive for the entire time a value can be retrieved. - * - * @see awaitable - */ -template <> -class basic_promise : public detail::promise::promise_base { -public: - using detail::promise::promise_base::promise_base; - using detail::promise::promise_base::operator=; /** - * @brief Set the promise to completed, and resume any awaiter. + * @brief Construct the result by move, and resume any awaiter. * + * @tparam Notify Whether to resume any awaiter or not. * @throws dpp::logic_exception if the promise is not empty. */ template + requires (std::is_void_v) void set_value() { - throw_if_not_empty(); - this->value.emplace<1>(); + this->throw_if_not_empty(); + this->value.template emplace<1>(); [[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order::acq_rel); if constexpr (Notify) { if (previous_value & detail::promise::sf_awaited) { @@ -606,7 +599,7 @@ class moveable_promise { * @copydoc basic_promise::set_value(const T&) */ template - void set_value(const T& v) requires (std::copy_constructible) { + void set_value(const detail::argument& v) requires (detail::is_copy_constructible) { shared_state->template set_value(v); } @@ -614,52 +607,24 @@ class moveable_promise { * @copydoc basic_promise::set_value(T&&) */ template - void set_value(T&& v) requires (std::move_constructible) { + void set_value(detail::argument&& v) requires (detail::is_move_constructible) { shared_state->template set_value(std::move(v)); } /** - * @copydoc basic_promise::set_value(T&&) + * @copydoc basic_promise::set_value() */ template - void set_exception(std::exception_ptr ptr) { - shared_state->template set_exception(std::move(ptr)); + void set_value() requires (std::is_void_v) { + shared_state->template set_value(); } /** - * @copydoc basic_promise::notify_awaiter - */ - void notify_awaiter() { - shared_state->notify_awaiter(); - } - - /** - * @copydoc basic_promise::get_awaitable - */ - awaitable get_awaitable() { - return shared_state->get_awaitable(); - } -}; - -template <> -class moveable_promise { - std::unique_ptr> shared_state = std::make_unique>(); - -public: - /** - * @copydoc basic_promise::set_value - */ - template - void set_value() { - shared_state->set_value(); - } - - /** - * @copydoc basic_promise::set_exception + * @copydoc basic_promise::set_value(T&&) */ template void set_exception(std::exception_ptr ptr) { - shared_state->set_exception(std::move(ptr)); + shared_state->template set_exception(std::move(ptr)); } /** @@ -672,7 +637,7 @@ class moveable_promise { /** * @copydoc basic_promise::get_awaitable */ - awaitable get_awaitable() { + awaitable get_awaitable() { return shared_state->get_awaitable(); } }; diff --git a/include/dpp/coro/coro.h b/include/dpp/coro/coro.h index cf5ab2cb51..5d9fefa9a6 100644 --- a/include/dpp/coro/coro.h +++ b/include/dpp/coro/coro.h @@ -19,8 +19,8 @@ * ************************************************************************************/ -#ifdef DPP_CORO #pragma once +#ifdef DPP_CORO #if (defined(_LIBCPP_VERSION) and !defined(__cpp_impl_coroutine)) // if libc++ experimental implementation (LLVM < 14) # define STDCORO_EXPERIMENTAL_HEADER @@ -113,6 +113,37 @@ decltype(auto) co_await_resolve(T&& expr) noexcept { return static_cast(expr); } +/** + * @brief Helper for when we need to pass an argument that may be void at parsing + * e.g. `void set_value(const detail::argument& v) requires (std::copy_constructible)`) would break without this + */ +template +using argument = std::conditional_t, std::monostate, std::type_identity_t>; + +/** + * @brief Helper because clang15 chokes on std::copy_constructible + */ +template +inline constexpr bool is_copy_constructible = std::copy_constructible; + +/** + * @brief Helper because clang15 chokes on std::copy_constructible + */ +template <> +inline constexpr bool is_copy_constructible = false; + +/** + * @brief Helper because clang15 chokes on std::move_constructible + */ +template +inline constexpr bool is_move_constructible = std::move_constructible; + +/** + * @brief Helper because clang15 chokes on std::move_constructible + */ +template <> +inline constexpr bool is_move_constructible = false; + #else /** diff --git a/include/dpp/coro/coroutine.h b/include/dpp/coro/coroutine.h index 0585544a47..a9e7a5d100 100644 --- a/include/dpp/coro/coroutine.h +++ b/include/dpp/coro/coroutine.h @@ -47,7 +47,7 @@ namespace detail { namespace coroutine { - template +template struct promise_t; template @@ -87,6 +87,9 @@ class [[nodiscard("dpp::coroutine only starts when it is awaited, it will do not coroutine(detail::coroutine::handle_t h) : handle{h} {} struct awaiter { + /** + * @brief Reference to the coroutine object being awaited. + */ coroutine &coro; /** @@ -117,6 +120,12 @@ class [[nodiscard("dpp::coroutine only starts when it is awaited, it will do not return coro.handle; } + /** + * @brief Final function called by the standard library when the coroutine is co_await-ed. + * + * Pops the coroutine's result and returns it. + * @remark Do not call this manually, use the co_await keyword instead. + */ R await_resume() { detail::coroutine::promise_t &promise = coro.handle.promise(); if (promise.exception) { diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h index f9a15ff9bb..1a1412be6b 100644 --- a/include/dpp/coro/task.h +++ b/include/dpp/coro/task.h @@ -375,7 +375,7 @@ struct promise_t : promise_base { /** * @brief Function called by the standard library when the coroutine co_returns * - * Does nothing but is required by the standard library. + * Sets the promise state to finished. */ void return_void() noexcept { set_value();