Skip to content

Instantly share code, notes, and snippets.

@cstratopoulos
Last active September 27, 2023 07:15
Show Gist options
  • 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:
boost::outcome_v2::result<std::size_t> read =
co_await stream.async_read(buffer, as_result(boost::asio::use_awaitable));
*/
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/detail/handler_alloc_helpers.hpp>
#include <boost/asio/detail/handler_cont_helpers.hpp>
#include <boost/asio/detail/handler_invoke_helpers.hpp>
#include <boost/outcome/outcome.hpp>
#include <boost/system/error_code.hpp>
#include <type_traits>
#include <utility>
template<typename CompletionToken>
struct as_result_t {
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 {
void operator()(const boost::system::error_code& ec)
{
using Result = boost::outcome_v2::result<void, boost::system::error_code>;
if (ec)
handler_(Result{ec});
else
handler_(Result{boost::outcome_v2::success()});
}
void operator()(std::exception_ptr ex)
{
using Result = boost::outcome_v2::result<void, std::exception_ptr>;
if (ex)
handler_(Result{ex});
else
handler_(Result{boost::outcome_v2::success()});
}
template<typename T>
void operator()(const boost::system::error_code& ec, T t)
{
using Result = boost::outcome_v2::result<T, boost::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 = boost::outcome_v2::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(
void* pointer, 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(boost::outcome_v2::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(boost::outcome_v2::result<void, std::exception_ptr>);
};
template<typename T>
struct result_signature<void(boost::system::error_code, T)> {
using type = void(boost::outcome_v2::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, T)> {};
template<typename T>
struct result_signature<void(std::exception_ptr, T)> {
using type = void(boost::outcome_v2::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:
using result_signature = ::detail::result_signature_t<Signature>;
using return_type =
typename async_result<CompletionToken, result_signature>::return_type;
template<typename Initiation, typename... Args>
static return_type
initiate(Initiation&& initiation, as_result_t<CompletionToken>&& token, Args&&... args)
{
return async_initiate<CompletionToken, result_signature>(
[init = std::forward<Initiation>(initiation)](
auto&& handler, auto&&... callArgs) mutable {
std::move(init)(
::detail::outcome_result_handler<std::decay_t<decltype(handler)>>{
std::forward<decltype(handler)>(handler)},
std::forward<decltype(callArgs)>(callArgs)...);
},
token.token_,
std::forward<Args>(args)...);
}
};
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()) 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()) noexcept
{
return associated_allocator<Handler, Allocator>::get(h.handler_, a);
}
};
} // namespace boost::asio
@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