Search code examples
c++c++20boost-asioasio

Boost asio using concrete executor type with c++20 coroutines causes compilation errors


I am implementing a very simple UDP client using the Boost ASIO library with C++20 coroutines.

To try to gain a little performance cheaply, I am trying to use a concrete executor type asio::io_service::executor_type instead of the polymorphic asio::any_io_executor.

Here is an example:

using executor_type = asio::io_context::executor_type;
using udp_resolver_type = asio::ip::basic_resolver<asio::ip::udp, executor_type>;

asio::io_context ctx;
udp_resolver_type resolver{ctx};

asio::awaitable<asio::ip::udp::endpoint, executor_type> host_to_endpoint(std::string host) {
    asio::ip::udp::resolver::query query{std::move(host), "1234"};
    const auto endpoints = co_await resolver.async_resolve(query, asio::use_awaitable);

    co_return *endpoints.begin();
}

However, a compilation error happens at the co_await resolver.async_resolve:

error: no matching member function for call to 'await_transform'
   13 |     const auto endpoints = co_await resolver.async_resolve(query, asio::use_awaitable);
note: candidate function not viable: no known conversion from 'decltype(asio::async_initiate<const asio::use_awaitable_t<> &, void (asio::error_code, results_type)>(declval<initiate_async_resolve>(), token, q))' (aka 'awaitable<asio::ip::basic_resolver_results<asio::ip::udp>, asio::any_io_executor>') to 'this_coro::executor_t' for 1st argument
  203 |   auto await_transform(this_coro::executor_t) noexcept
... several other candidate functions ...
note: candidate template ignored: could not match 'asio::io_context::basic_executor_type<std::allocator<void>, 0>' against 'asio::any_io_executor'
  168 |   auto await_transform(awaitable<T, Executor> a) const
note: candidate template ignored: substitution failure [with Op = decltype(asio::async_initiate<const asio::use_awaitable_t<> &, void (asio::error_code, results_type)>(declval<initiate_async_resolve>(), token, q))]: no type named 'type' in 'asio::constraint<false>'
  177 |   auto await_transform(Op&& op,

If I replace the template argument E of asio::awaitable<T, E> from executor_type to the polymorphic asio::any_io_executor then it compiles just fine.

My question is, to avoid the cost of the polymorphic asio::any_io_executor, am I correct to assume that the function host_to_endpoint should return asio::awaitable<T, E> with the template argument of the concrete E = executor_type? If yes, how can I fix my code so that it compiles with the concrete executor?


Solution

  • The completion token is congruent with the surrounding coro context (promise type). Therefore you might want to:

    asio::use_awaitable_t<executor_type> use_awaitable;
    auto endpoints = co_await resolver.async_resolve(std::move(host), "1234", use_awaitable);
    

    (Note that resolver::query is deprecated).

    However, for reasons of efficiency always prefer asio::deferred anyways, which will automatically be await-transformed by your coro context:

    auto endpoints = co_await resolver.async_resolve(std::move(host), "1234", asio::deferred);
    

    In fact, in most recent Asio versions, deferred has been made the global default completion token, meaning that all Asio's initiation functions can simply omit the completion token:

    auto endpoints = co_await resolver.async_resolve(std::move(host), "1234");
    

    Live a simple life, live a happy life :)