// // experimental/awaitable_operators.hpp // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP #define ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include "asio/detail/config.hpp" #include #include #include #include #include "asio/awaitable.hpp" #include "asio/co_spawn.hpp" #include "asio/detail/type_traits.hpp" #include "asio/experimental/deferred.hpp" #include "asio/experimental/parallel_group.hpp" #include "asio/multiple_exceptions.hpp" #include "asio/this_coro.hpp" #include "asio/detail/push_options.hpp" namespace asio { namespace experimental { namespace awaitable_operators { namespace detail { template awaitable awaitable_wrap(awaitable a, typename constraint::value>::type* = 0) { return a; } template awaitable, Executor> awaitable_wrap(awaitable a, typename constraint::value>::type* = 0) { co_return std::optional(co_await std::move(a)); } template T& awaitable_unwrap(typename conditional::type& r, typename constraint::value>::type* = 0) { return r; } template T& awaitable_unwrap(std::optional::type>& r, typename constraint::value>::type* = 0) { return *r; } } // namespace detail /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable operator&&( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, ex1] = co_await make_parallel_group( co_spawn(ex, std::move(t), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return; } /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable operator&&( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, std::move(t), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return std::move(detail::awaitable_unwrap(r1)); } /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable operator&&( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return std::move(detail::awaitable_unwrap(r0)); } /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable, Executor> operator&&( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return std::make_tuple( std::move(detail::awaitable_unwrap(r0)), std::move(detail::awaitable_unwrap(r1))); } /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable, Executor> operator&&( awaitable, Executor> t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return std::move(detail::awaitable_unwrap>(r0)); } /// Wait for both operations to succeed. /** * If one operations fails, the other is cancelled as the AND-condition can no * longer be satisfied. */ template awaitable, Executor> operator&&( awaitable, Executor> t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_error(), deferred ); if (ex0 && ex1) throw multiple_exceptions(ex0); if (ex0) std::rethrow_exception(ex0); if (ex1) std::rethrow_exception(ex1); co_return std::tuple_cat( std::move(detail::awaitable_unwrap>(r0)), std::make_tuple(std::move(detail::awaitable_unwrap(r1)))); } /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, ex1] = co_await make_parallel_group( co_spawn(ex, std::move(t), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_success(), deferred ); if (order[0] == 0) { if (!ex0) co_return std::variant{ std::in_place_index<0>}; if (!ex1) co_return std::variant{ std::in_place_index<1>}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index<1>}; if (!ex0) co_return std::variant{ std::in_place_index<0>}; throw multiple_exceptions(ex1); } } /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, std::move(t), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_success(), deferred ); if (order[0] == 0) { if (!ex0) co_return std::variant{ std::in_place_index<0>}; if (!ex1) co_return std::variant{ std::in_place_index<1>, std::move(detail::awaitable_unwrap(r1))}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index<1>, std::move(detail::awaitable_unwrap(r1))}; if (!ex0) co_return std::variant{ std::in_place_index<0>}; throw multiple_exceptions(ex1); } } /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_success(), deferred ); if (order[0] == 0) { if (!ex0) co_return std::variant{ std::in_place_index<0>, std::move(detail::awaitable_unwrap(r0))}; if (!ex1) co_return std::variant{ std::in_place_index<1>}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index<1>}; if (!ex0) co_return std::variant{ std::in_place_index<0>, std::move(detail::awaitable_unwrap(r0))}; throw multiple_exceptions(ex1); } } /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_success(), deferred ); if (order[0] == 0) { if (!ex0) co_return std::variant{ std::in_place_index<0>, std::move(detail::awaitable_unwrap(r0))}; if (!ex1) co_return std::variant{ std::in_place_index<1>, std::move(detail::awaitable_unwrap(r1))}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index<1>, std::move(detail::awaitable_unwrap(r1))}; if (!ex0) co_return std::variant{ std::in_place_index<0>, std::move(detail::awaitable_unwrap(r0))}; throw multiple_exceptions(ex1); } } namespace detail { template struct widen_variant { template static std::variant call(SourceVariant& source) { if (source.index() == I) return std::variant{ std::in_place_index, std::move(std::get(source))}; else if constexpr (I + 1 < std::variant_size_v) return call(source); else throw std::logic_error("empty variant"); } }; } // namespace detail /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable, Executor> t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, std::move(u), deferred) ).async_wait( wait_for_one_success(), deferred ); using widen = detail::widen_variant; if (order[0] == 0) { if (!ex0) co_return widen::template call<0>( detail::awaitable_unwrap>(r0)); if (!ex1) co_return std::variant{ std::in_place_index}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index}; if (!ex0) co_return widen::template call<0>( detail::awaitable_unwrap>(r0)); throw multiple_exceptions(ex1); } } /// Wait for one operation to succeed. /** * If one operations succeeds, the other is cancelled as the OR-condition is * already satisfied. */ template awaitable, Executor> operator||( awaitable, Executor> t, awaitable u) { auto ex = co_await this_coro::executor; auto [order, ex0, r0, ex1, r1] = co_await make_parallel_group( co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred), co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred) ).async_wait( wait_for_one_success(), deferred ); using widen = detail::widen_variant; if (order[0] == 0) { if (!ex0) co_return widen::template call<0>( detail::awaitable_unwrap>(r0)); if (!ex1) co_return std::variant{ std::in_place_index, std::move(detail::awaitable_unwrap(r1))}; throw multiple_exceptions(ex0); } else { if (!ex1) co_return std::variant{ std::in_place_index, std::move(detail::awaitable_unwrap(r1))}; if (!ex0) co_return widen::template call<0>( detail::awaitable_unwrap>(r0)); throw multiple_exceptions(ex1); } } } // namespace awaitable_operators } // namespace experimental } // namespace asio #include "asio/detail/pop_options.hpp" #endif // ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP