What's going on with asio::async_compose?
07:42 25 Dec 2025

The following code snippet produces a compilation error: error: deduced type 'void' for 'err' is incomplete.

What I don't understand is, shouldn't errbe of type error_code?

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

namespace asio = boost::asio;
namespace sys = boost::system;

template
void debugln(std::FILE* stream, std::format_string fmt, Args&&... args)
{
    std::string s = std::format(fmt, std::forward(args)...);
    std::fprintf(stream, "%s\n", s.c_str());
}

template
void debugln(std::format_string fmt, Args&&... args)
{
    debugln(stdout, fmt, std::forward(args)...);
}

namespace detail {
struct connect_op
{
    std::string_view host_;
    std::string_view port_;
    asio::ip::tcp::socket* sock_;
    asio::ip::tcp::resolver resolv_;

    connect_op(std::string_view host, std::string_view port, asio::ip::tcp::socket* tcp_sock)
        : resolv_(tcp_sock->get_executor())
    {
        host_ = host;
        port_ = port;
        sock_ = tcp_sock;
    }

    // This overload will be called after the async_resolve completes
    template
    void operator()(Self& self, sys::error_code ec, asio::ip::tcp::resolver::results_type endpoints)
    {
        if (ec) {
            debugln("(connect_op) async_resolve failed: {}", ec.message());
            return self.complete(ec);
        } else {
            debugln("(connect_op) async_resolve succeeded: {}", endpoints.size());
            asio::async_connect(*sock_, std::move(endpoints), std::move(self));
        }
    }

    // This overload will be called after the async_connect completes
    template
    void operator()(
        Self& self,
        sys::error_code ec,
        const asio::ip::tcp::endpoint& selected_endpoint
    )
    {
        if (ec) {
            debugln("(connect_op) async_connect failed: {}", ec.message());
        } else {
            debugln("(connect_op) async connected to {}", selected_endpoint.address().to_string());
        }

        self.complete(ec);
    }

    // This overload will be used for the initiation completion handler
    // `async_compose` will cause the implementation (in this case, `connect_op`) to be called once
    // with no arguments (other than self) during initiation
    template
    void operator()(Self& self, sys::error_code ec = {})
    {
        boost::ignore_unused(ec);
        resolv_.async_resolve(host_, port_, std::move(self));
    }
};
} // namespace detail

class tcp_connection
{
public:
    /// Executor type.
    using executor_type = asio::any_io_executor;

    tcp_connection(std::string_view host, std::string_view port, executor_type ex)
        : host_(host), port_(port), sock_(ex, asio::ip::tcp::v4())
    {
    }

    tcp_connection(std::string_view host, std::string_view port, asio::io_context& ioc)
        : tcp_connection(host, port, ioc.get_executor())
    {
    }

    /// Returns the underlying executor.
    executor_type get_executor() noexcept { return sock_.get_executor(); }

    template
    auto async_connect(CompletionToken&& token = {})
    {
        return asio::async_compose(
            detail::connect_op(host_, port_, &sock_),
            token,
            sock_.get_executor()
        );
    }

private:
    std::string host_;
    std::string port_;
    asio::ip::tcp::socket sock_;
};

asio::awaitable co_main(std::string_view host, std::string_view port)
{
    auto exector = co_await asio::this_coro::executor;

    auto conn = tcp_connection(host, port, exector);

    // ERROR: deduced type 'void' for 'err' is incomplete
    // why err is void?
    auto err = co_await conn.async_connect();

    co_return;
}

int main(int argc, char* argv[])
{
    if (argc < 3) {
        std::printf("Usage: %s  \n", argv[0]);
        return 1;
    }

    std::string host = argv[1];
    std::string port = argv[2];

    try {
        asio::io_context ioc;
        asio::co_spawn(ioc, co_main(host, port), [](std::exception_ptr p) {
            if (p) {
                std::rethrow_exception(p);
            }
        });
        ioc.run();
        return 0;
    } catch (std::exception const& e) {
        debugln("(main) {}", e.what());
        return 1;
    }
}
boost asio