537 lines
15 KiB
C++
537 lines
15 KiB
C++
//
|
|
// 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 <optional>
|
|
#include <stdexcept>
|
|
#include <tuple>
|
|
#include <variant>
|
|
#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 <typename T, typename Executor>
|
|
awaitable<T, Executor> awaitable_wrap(awaitable<T, Executor> a,
|
|
typename constraint<is_constructible<T>::value>::type* = 0)
|
|
{
|
|
return a;
|
|
}
|
|
|
|
template <typename T, typename Executor>
|
|
awaitable<std::optional<T>, Executor> awaitable_wrap(awaitable<T, Executor> a,
|
|
typename constraint<!is_constructible<T>::value>::type* = 0)
|
|
{
|
|
co_return std::optional<T>(co_await std::move(a));
|
|
}
|
|
|
|
template <typename T>
|
|
T& awaitable_unwrap(typename conditional<true, T, void>::type& r,
|
|
typename constraint<is_constructible<T>::value>::type* = 0)
|
|
{
|
|
return r;
|
|
}
|
|
|
|
template <typename T>
|
|
T& awaitable_unwrap(std::optional<typename conditional<true, T, void>::type>& r,
|
|
typename constraint<!is_constructible<T>::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 <typename Executor>
|
|
awaitable<void, Executor> operator&&(
|
|
awaitable<void, Executor> t, awaitable<void, Executor> 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 <typename U, typename Executor>
|
|
awaitable<U, Executor> operator&&(
|
|
awaitable<void, Executor> t, awaitable<U, Executor> 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<U>(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 <typename T, typename Executor>
|
|
awaitable<T, Executor> operator&&(
|
|
awaitable<T, Executor> t, awaitable<void, Executor> 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<T>(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 <typename T, typename U, typename Executor>
|
|
awaitable<std::tuple<T, U>, Executor> operator&&(
|
|
awaitable<T, Executor> t, awaitable<U, Executor> 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<T>(r0)),
|
|
std::move(detail::awaitable_unwrap<U>(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 <typename... T, typename Executor>
|
|
awaitable<std::tuple<T..., std::monostate>, Executor> operator&&(
|
|
awaitable<std::tuple<T...>, Executor> t, awaitable<void, Executor> 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<std::tuple<T...>>(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 <typename... T, typename U, typename Executor>
|
|
awaitable<std::tuple<T..., U>, Executor> operator&&(
|
|
awaitable<std::tuple<T...>, Executor> t, awaitable<U, Executor> 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<std::tuple<T...>>(r0)),
|
|
std::make_tuple(std::move(detail::awaitable_unwrap<U>(r1))));
|
|
}
|
|
|
|
/// Wait for one operation to succeed.
|
|
/**
|
|
* If one operations succeeds, the other is cancelled as the OR-condition is
|
|
* already satisfied.
|
|
*/
|
|
template <typename Executor>
|
|
awaitable<std::variant<std::monostate, std::monostate>, Executor> operator||(
|
|
awaitable<void, Executor> t, awaitable<void, Executor> 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::monostate, std::monostate>{
|
|
std::in_place_index<0>};
|
|
if (!ex1)
|
|
co_return std::variant<std::monostate, std::monostate>{
|
|
std::in_place_index<1>};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<std::monostate, std::monostate>{
|
|
std::in_place_index<1>};
|
|
if (!ex0)
|
|
co_return std::variant<std::monostate, std::monostate>{
|
|
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 <typename U, typename Executor>
|
|
awaitable<std::variant<std::monostate, U>, Executor> operator||(
|
|
awaitable<void, Executor> t, awaitable<U, Executor> 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::monostate, U>{
|
|
std::in_place_index<0>};
|
|
if (!ex1)
|
|
co_return std::variant<std::monostate, U>{
|
|
std::in_place_index<1>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<std::monostate, U>{
|
|
std::in_place_index<1>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
if (!ex0)
|
|
co_return std::variant<std::monostate, U>{
|
|
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 <typename T, typename Executor>
|
|
awaitable<std::variant<T, std::monostate>, Executor> operator||(
|
|
awaitable<T, Executor> t, awaitable<void, Executor> 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<T, std::monostate>{
|
|
std::in_place_index<0>,
|
|
std::move(detail::awaitable_unwrap<T>(r0))};
|
|
if (!ex1)
|
|
co_return std::variant<T, std::monostate>{
|
|
std::in_place_index<1>};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<T, std::monostate>{
|
|
std::in_place_index<1>};
|
|
if (!ex0)
|
|
co_return std::variant<T, std::monostate>{
|
|
std::in_place_index<0>,
|
|
std::move(detail::awaitable_unwrap<T>(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 <typename T, typename U, typename Executor>
|
|
awaitable<std::variant<T, U>, Executor> operator||(
|
|
awaitable<T, Executor> t, awaitable<U, Executor> 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<T, U>{
|
|
std::in_place_index<0>,
|
|
std::move(detail::awaitable_unwrap<T>(r0))};
|
|
if (!ex1)
|
|
co_return std::variant<T, U>{
|
|
std::in_place_index<1>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<T, U>{
|
|
std::in_place_index<1>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
if (!ex0)
|
|
co_return std::variant<T, U>{
|
|
std::in_place_index<0>,
|
|
std::move(detail::awaitable_unwrap<T>(r0))};
|
|
throw multiple_exceptions(ex1);
|
|
}
|
|
}
|
|
|
|
namespace detail {
|
|
|
|
template <typename... T>
|
|
struct widen_variant
|
|
{
|
|
template <std::size_t I, typename SourceVariant>
|
|
static std::variant<T...> call(SourceVariant& source)
|
|
{
|
|
if (source.index() == I)
|
|
return std::variant<T...>{
|
|
std::in_place_index<I>, std::move(std::get<I>(source))};
|
|
else if constexpr (I + 1 < std::variant_size_v<SourceVariant>)
|
|
return call<I + 1>(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 <typename... T, typename Executor>
|
|
awaitable<std::variant<T..., std::monostate>, Executor> operator||(
|
|
awaitable<std::variant<T...>, Executor> t, awaitable<void, Executor> 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<T..., std::monostate>;
|
|
if (order[0] == 0)
|
|
{
|
|
if (!ex0)
|
|
co_return widen::template call<0>(
|
|
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
|
if (!ex1)
|
|
co_return std::variant<T..., std::monostate>{
|
|
std::in_place_index<sizeof...(T)>};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<T..., std::monostate>{
|
|
std::in_place_index<sizeof...(T)>};
|
|
if (!ex0)
|
|
co_return widen::template call<0>(
|
|
detail::awaitable_unwrap<std::variant<T...>>(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 <typename... T, typename U, typename Executor>
|
|
awaitable<std::variant<T..., U>, Executor> operator||(
|
|
awaitable<std::variant<T...>, Executor> t, awaitable<U, Executor> 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<T..., U>;
|
|
if (order[0] == 0)
|
|
{
|
|
if (!ex0)
|
|
co_return widen::template call<0>(
|
|
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
|
if (!ex1)
|
|
co_return std::variant<T..., U>{
|
|
std::in_place_index<sizeof...(T)>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
throw multiple_exceptions(ex0);
|
|
}
|
|
else
|
|
{
|
|
if (!ex1)
|
|
co_return std::variant<T..., U>{
|
|
std::in_place_index<sizeof...(T)>,
|
|
std::move(detail::awaitable_unwrap<U>(r1))};
|
|
if (!ex0)
|
|
co_return widen::template call<0>(
|
|
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
|
throw multiple_exceptions(ex1);
|
|
}
|
|
}
|
|
|
|
} // namespace awaitable_operators
|
|
} // namespace experimental
|
|
} // namespace asio
|
|
|
|
#include "asio/detail/pop_options.hpp"
|
|
|
|
#endif // ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP
|