1

Clang tells me there is no viable conversion from the return value of boost::beast::http::async_write with completion token boost::asio::as_tuple(boost::asio::deferred) to awaitable<tuple<boost::system::error_code, unsigned long>>:

error: no viable conversion from returned value of type 'decltype(enable_if_t<enable_if_t<detail::are_completion_signatures<void (error_code, unsigned long)>::value, detail::async_result_has_initiate_memfn<as_tuple_t<deferred_t>, void (error_code, unsigned long)>>::value, async_result<decay_t<as_tuple_t<deferred_t>>, void (error_code, unsigned long)>>::initiate(static_cast<boost::beast::http::detail::run_write_msg_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>> &&>(initiation), static_cast<boost::asio::as_tuple_t<boost::asio::deferred_t> &&>(token), static_cast<const boost::beast::http::message<false, boost::beast::http::basic_string_body<char>> *&&>(args), static_cast<std::integral_constant<bool, true> &&>(args)))' (aka 'deferred_async_operation<void (std::tuple<boost::system::error_code, unsigned long>), boost::asio::async_result<boost::asio::as_tuple_t<boost::asio::deferred_t>, void (boost::system::error_code, unsigned long)>::init_wrapper<boost::beast::http::detail::run_write_msg_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>>, const boost::beast::http::message<false, boost::beast::http::basic_string_body<char, std::char_traits<char>, std::allocator<char>>, boost::beast::http::basic_fields<std::allocator<char>>> *, std::integral_constant<bool, true>>') to function return type 'boost::asio::awaitable<std::tuple<boost::system::error_code, std::size_t>>' (aka 'awaitable<tuple<boost::system::error_code, unsigned long>>')
  179 |return boost::beast::http::async_write(stream, typed_message, boost::asio::as_tuple(boost::asio::deferred));
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The function in question looks like this:

template<typename stream_t>
boost::asio::awaitable<void> response_worker(boost::asio::experimental::channel<void(boost::system::error_code, http_response_t)> &channel, stream_t &stream)
{
    while (true) {
        boost::system::error_code ec;
        std::variant<
            boost::beast::http::response<boost::beast::http::string_body>,
            boost::beast::http::response<boost::beast::http::empty_body>,
            boost::beast::http::response<boost::beast::http::dynamic_body>,
            boost::beast::http::response<boost::beast::http::file_body>,
            boost::beast::http::response<boost::beast::http::buffer_body>>
            message = co_await channel.async_receive(boost::asio::redirect_error(boost::asio::deferred, ec));
        if (ec == boost::asio::error::eof) {
            channel.close();
            co_return;
        }
        const auto [ec2, transferred] = co_await std::visit(
            [&stream](auto &typed_message) -> boost::asio::awaitable<std::tuple<boost::system::error_code, std::size_t>> {
                return boost::beast::http::async_write(stream, typed_message, boost::asio::as_tuple(boost::asio::deferred));
            },
            message);
        //error handling
    }
}

This used to compile and I'm incredibly confused by the error message, is there an error in my code or is my clang installation even more broken than I thought (libclang does not get the header search correct means I have to add -I/usr/lib/clang/19/include as a compiler flag if I understand it right)?

2 Answers 2

2

Reading the compiler message:

error: no viable conversion from returned value of type 'decltype(...)` (aka 
    deferred_async_operation<
        void(std::tuple<boost::system::error_code, unsigned long>),
        asio::async_result<asio::as_tuple_t<asio::deferred_t>,
                                  void(boost::system::error_code, unsigned long)>::
            init_wrapper<http::detail::run_write_msg_op<
                asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>>>,
        http::message<
            false, http::basic_string_body<char, std::char_traits<char>, std::allocator<char>>,
            http::basic_fields<std::allocator<char>>> const*,
        std::integral_constant<bool, true>>'
          )
              to function return type
      'asio::awaitable<std::tuple<boost::system::error_code, std::size_t>>'
      return http::async_write(stream, typed_message,
                                             asio::as_tuple(asio::deferred));

It looks like you may have changed from asio::use_awaitable (which does return an awaitable promise type), to asio::deferred (which results in a deferred async operation).

This is good, because

  1. deferred became the default completion token
  2. deferred incurs less overhead when called ("transformed") from inside an Asio coroutine frame (awaitable promise type)

This means you will have to await the deferred operation to get a compatible return type. Note also that recent Asio versions have included partial applications of the token adaptors so you can spell it like so:

Live On Coliru

while (true) {
    if (auto [ec, message] = co_await channel.async_receive(asio::as_tuple); ec == asio::error::eof) {
        co_return channel.close();
    } else if (auto [ec, transferred] = co_await std::visit(
                   [&](auto& typed_message) -> asio::awaitable<std::tuple<error_code, std::size_t>> {
                       co_return co_await async_write(stream, typed_message, asio::as_tuple);
                   },
                   message);
               ec.failed()) //
    {
        // error handling
    } else {
        // success handling
    }
}

HOLD ON

However, please note that you don't need to be so ... clumsy about variant response objects. You can type-erase the response using a BuffersGenerator. The implementation that supports all message<> instances is called http::message_generator. E.g.:

#include <boost/asio.hpp>
#include <boost/asio/experimental/channel.hpp>
#include <boost/beast.hpp>
namespace asio = boost::asio;

namespace http        = boost::beast::http;
using http_response_t = std::optional<http::message_generator>;
using channel_t       = asio::experimental::channel<void(boost::system::error_code, http_response_t)>;

template <typename stream_t> asio::awaitable<void> response_worker(channel_t& channel, stream_t& stream) {
    using boost::beast::error_code;

    while (true) {
        if (auto [ec, response] = co_await channel.async_receive(asio::as_tuple); ec == asio::error::eof) {
            co_return channel.close();
        } else if (response.has_value()) {
            if (auto [ec, transferred] =
                    co_await boost::beast::async_write(stream, std::move(*response), asio::as_tuple);
                ec.failed()) //
            {
                // error handling
            } else {
                // success handling
            }
        }
    }
}

int main() {
    asio::io_context      io_context;
    asio::ip::tcp::socket socket(io_context); // TODO connect
    channel_t             channel(io_context);

    co_spawn(io_context, response_worker(channel, socket), asio::detached);

    io_context.run();
}
Sign up to request clarification or add additional context in comments.

2 Comments

The change I did was in fact from co_return co_await to return but somehow I had forgotten about that change. Your Coliru link says "internal compiler error: File size limit exceeded signal terminated program" for me, does the code compile successfully for you? If it does my installation is probably broken as clang now tells me: /usr/include/boost/intrusive/bstree_algorithms.hpp:758:22: error: no matching function for call to 'uncast' 758 | node_ptr end = detail::uncast(header); (Which was the reason for the change in the first place)
Yeah it does compile. Coliru is obviously resource constrained
0

Turns out this was a compiler bug in the version of Clang I was using.

You can see in this godbolt example how the minimally reproducible example compiles in Clang 20.1.0 but not Clang 19.1.10.

The issue was caused by having the code contained in a template inside a module.

  • File CMakeLists.txt

    cmake_minimum_required(VERSION 3.31)
    
    project(
      experiments
      VERSION 0.0.0
      LANGUAGES CXX)
    
    
    set(CMAKE_CXX_STANDARD 23)
    
    add_executable(TEST
        main.cpp)
    target_sources(TEST PUBLIC FILE_SET CXX_MODULES FILES
        test.cppm)
    
  • File test.cppm

    //test.cppm
    module;
    #include <boost/beast.hpp>
    export module test;
    
    export namespace test {
     template<typename arg_t>
     void run(arg_t &&arg)
     {
         using http_response_t = std::variant<
             boost::beast::http::response<boost::beast::http::empty_body>,
             boost::beast::http::response<boost::beast::http::string_body>,
             boost::beast::http::response<boost::beast::http::dynamic_body>,
             boost::beast::http::response<boost::beast::http::file_body>,
             boost::beast::http::response<boost::beast::http::buffer_body>>;
    
         (void) arg;
         http_response_t resp;
         auto message = std::visit(
             []<typename body_t>(boost::beast::http::response<body_t> response) {
                 return boost::beast::http::message_generator{std::move(response)};
             },
             std::move(resp));
         (void) message;
     }
    } // namespace test
    
    
  • File main.cpp

    //main.cpp
    import test;
    
    int main()
    {
     test::run(5);
    }
    

1 Comment

5 months after the fact – I thought I was slow updating things :) Anyhoops, thanks for posting. You mention that it is 100% a bug tied to module support: godbolt.org/z/9njWjaK61 Without modules there was no problem. That was never part of the question, so technically this answer is "not correct" for the question. (I do appreciate that the question was missing essential details, not the answer)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.