Skip to content

Instantly share code, notes, and snippets.

@cstratopoulos
Last active September 27, 2023 07:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cstratopoulos/901b5cdd41d07c6ce6d83798b09ecf9b to your computer and use it in GitHub Desktop.
Save cstratopoulos/901b5cdd41d07c6ce6d83798b09ecf9b to your computer and use it in GitHub Desktop.
Boost ASIO async_result for outcome type
/*
A Boost.ASIO async_result adapter for outcome (https://github.com/ned14/outcome)
Sample use:
auto token = co_await boost::asio::experimental::this_coro::token();
outcome::result<std::size_t> read = co_await beast::http::async_read(stream, buffer, request, as_result(token));
*/
#include <boost/system/error_code.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/detail/handler_alloc_helpers.hpp>
#include <boost/asio/detail/handler_invoke_helpers.hpp>
#include <boost/system/error_code.hpp>
#include <outcome.hpp>
#include <type_traits>
#include <utility>
namespace outcome = OUTCOME_V2_NAMESPACE;
template <typename CompletionToken>
struct as_result_t {
template <typename T>
as_result_t(T&& completion_token) : token_{ std::forward<T>(completion_token) } {}
CompletionToken token_;
};
template <typename CompletionToken>
inline as_result_t<std::decay_t<CompletionToken>>
as_result(CompletionToken&& completion_token)
{
return as_result_t<std::decay_t<CompletionToken>>{
std::forward<CompletionToken>(completion_token)
};
}
namespace detail {
// Class to adapt as_outcome_t as a completion handler
template <typename Handler>
struct outcome_result_handler {
template <typename CompletionToken>
outcome_result_handler(as_result_t<CompletionToken> e)
: handler_{ std::forward<CompletionToken>(e.token_) }
{}
void operator()(const boost::system::error_code& ec)
{
using Result = outcome::result<void, system::error_code>;
if (ec)
handler_(Result{ ec });
else
handler_(Result{ outcome::success() });
}
void operator()(std::exception_ptr ex)
{
using Result = outcome::result<void, std::exception_ptr>;
if (ex)
handler_(Result{ ex });
else
handler_(Result{ outcome::success() });
}
template<typename T>
void operator()(const system::error_code& ec, T t)
{
using Result = outcome::result<T, system::error_code>;
if (ec)
handler_(Result{ ec });
else
handler_(Result{ std::move(t) });
}
template<typename T>
void operator()(std::exception_ptr ex, T t)
{
using Result = outcome::result<T, std::exception_ptr>;
if (ex)
handler_(Result{ ex });
else
handler_(Result{ std::move(t) });
}
Handler handler_;
};
template <typename Handler>
inline void* asio_handler_allocate(std::size_t size,
outcome_result_handler<Handler>* this_handler)
{
return boost_asio_handler_alloc_helpers::allocate(
size, this_handler->handler_);
}
template <typename Handler>
inline void* asio_handler_deallocate(std::size_t size,
outcome_result_handler<Handler>* this_handler)
{
boost_asio_handler_alloc_helpers::deallocate(
pointer, size, this_handler->handler_);
}
template <typename Handler>
inline bool asio_handler_is_continuation(
outcome_result_handler<Handler>* this_handler)
{
return boost_asio_handler_cont_helpers::is_continuation(
this_handler->handler_);
}
template <typename Function, typename Handler>
inline void asio_handler_invoke(Function& function,
outcome_result_handler<Handler>* this_handler)
{
boost_asio_handler_invoke_helpers::invoke(
function, this_handler->handler_);
}
template <typename Function, typename Handler>
inline void asio_handler_invoke(const Function& function,
outcome_result_handler<Handler>* this_handler)
{
boost_asio_handler_invoke_helpers::invoke(
function, this_handler->handler_);
}
template <typename Signature>
struct result_signature {};
template <>
struct result_signature<void(boost::system::error_code)> {
using type = void(outcome::result<void, boost::system::error_code>);
};
template <>
struct result_signature<void(const boost::system::error_code&)>
: result_signature<void(boost::system::error_code)>
{};
template <>
struct result_signature<void(std::exception_ptr)> {
using type = void(outcome::result<void, std::exception_ptr>);
};
template <typename T>
struct result_signature<void(boost::system::error_code, T)> {
using type = void(outcome::result<T, boost::system::error_code>);
};
template <typename T>
struct result_signature<void(const boost::system::error_code&, T)>
: result_signature<void(boost::system::error_code)>
{};
template <typename T>
struct result_signature<void(std::exception_ptr, T)> {
using type = void(outcome::result<T, std::exception_ptr>);
};
template <typename Signature>
using result_signature_t = typename result_signature<Signature>::type;
} // namespace detail
namespace boost::asio {
template <typename CompletionToken, typename Signature>
class async_result<as_result_t<CompletionToken>, Signature> :
public async_result<
CompletionToken, detail::result_signature_t<Signature>> {
public:
using base_result = async_result<
CompletionToken, detail::result_signature_t<Signature>>;
using completion_handler_type = detail::outcome_result_handler<
typename base_result::completion_handler_type
>;
using return_type = typename base_result::return_type;
explicit async_result(completion_handler_type& h)
: base_result{ h.handler_ }
{}
using base_result::get;
};
template <typename Handler, typename Executor>
struct associated_executor<detail::outcome_result_handler<Handler>, Executor>
{
typedef typename associated_executor<Handler, Executor>::type type;
static type get(const detail::outcome_result_handler<Handler>& h,
const Executor& ex = Executor()) BOOST_ASIO_NOEXCEPT
{
return associated_executor<Handler, Executor>::get(h.handler_, ex);
}
};
template <typename Handler, typename Allocator>
struct associated_allocator<detail::outcome_result_handler<Handler>, Allocator>
{
typedef typename associated_allocator<Handler, Allocator>::type type;
static type get(const detail::outcome_result_handler<Handler>& h,
const Allocator& a = Allocator()) BOOST_ASIO_NOEXCEPT
{
return associated_allocator<Handler, Allocator>::get(h.handler_, a);
}
};
}
@MHebes
Copy link

MHebes commented Aug 11, 2022

This is awesome. The definition of as_result_t can be changed to include default executors like as_tuple_t has:

using awaitable_socket = as_result_t<asio::use_awaitable_t<>>::as_default_on_t<asio::ip::tcp::socket>;
awaitable_socket socket(ctx);
// ...
auto sent_result = co_await async_connect(socket, endpoints);
template <typename CompletionToken>
struct as_result_t {
  /// Tag type used to prevent the "default" constructor from being used for
  /// conversions.
  struct default_constructor_tag {};

  /// Default constructor.
  /**
   * This constructor is only valid if the underlying completion token is
   * default constructible and move constructible. The underlying completion
   * token is itself defaulted as an argument to allow it to capture a source
   * location.
   */
  constexpr as_result_t(default_constructor_tag = default_constructor_tag(),
                          CompletionToken token = CompletionToken())
      : token_(std::move(token)) {}

  /// Constructor.
  template <typename T>
  constexpr explicit as_result_t(T&& completion_token)
      : token_(std::forward<T>(completion_token)) {}
  /// Adapts an executor to add the @c as_result_t completion token as the
  /// default.
  template <typename InnerExecutor>
  struct executor_with_default : InnerExecutor {
    /// Specify @c as_result_t as the default completion token type.
    typedef as_result_t default_completion_token_type;

    /// Construct the adapted executor from the inner executor type.
    template <typename InnerExecutor1>
    executor_with_default(
        InnerExecutor1&& ex,
        typename std::enable_if<
            std::conditional<
                !std::is_same<InnerExecutor1, executor_with_default>::value,
                std::is_convertible<InnerExecutor1, InnerExecutor>,
                std::false_type>::type::value,
            int>::type = 0) noexcept
        : InnerExecutor(std::forward<InnerExecutor1>(ex)) {}
  };

  /// Type alias to adapt an I/O object to use @c as_result_t as its
  /// default completion token type.
  template <typename T>
  using as_default_on_t = typename T::template rebind_executor<
      executor_with_default<typename T::executor_type>>::other;

  /// Function helper to adapt an I/O object to use @c as_result_t as its
  /// default completion token type.
  template <typename T>
  static typename std::decay<T>::type::template rebind_executor<
      executor_with_default<typename std::decay<T>::type::executor_type>>::other
  as_default_on(T&& object) {
    return typename std::decay<T>::type::template rebind_executor<
        executor_with_default<typename std::decay<T>::type::executor_type>>::
        other(std::forward<T>(object));
  }

  CompletionToken token_;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment