You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写适配不同CompletionToken签名的Boost.Asio无栈协程代码?

适配不同CompletionToken签名的Boost.Asio无栈协程最佳实现方式

问题背景

Boost.Asio的异步函数对应不同的CompletionToken签名,例如:

  • boost::asio::async_write对应的WriteToken处理函数签名:
void write_handler(
    const boost::system::error_code& ec,
    std::size_t bytes_transferred)
  • boost::asio::ip::tcp::resolver::async_resolve对应的ResolveToken处理函数签名:
void resolve_handler(
    const boost::system::error_code& ec,
    boost::asio::ip::tcp::resolver::results_type results);

二者首个参数均为const boost::system::error_code&,但第二个参数类型不同。而Boost.Asio的无栈协程要求operator()的签名匹配所有异步操作的CompletionToken签名,这就导致async_write和async_resolve的签名无法直接兼容。

环境

  • C++17
  • Boost 1.82.0

已尝试的实现方式

1. 使用std::any

通过std::any统一第二个参数类型,需要用std::any_cast做类型转换,类型不匹配时会抛出异常,整体效果尚可:

#include <iostream>
#include <any>

#include <boost/asio.hpp>

#include <boost/asio/yield.hpp>

struct myapp {
    myapp(
        boost::asio::ip::tcp::resolver& res,
        boost::asio::ip::tcp::socket& sock
    ): res{res}, sock{sock}
    {}

    void operator()(
        boost::system::error_code const& ec = boost::system::error_code{},
        std::any second = std::any{}
    ) {
        reenter (coro) {
            // resolve
            yield res.async_resolve("localhost", "1883", *this);
            std::cout << "async_resolve:" << ec.message() << std::endl;
            if (ec) return;

            // connect
            yield {
                auto results = std::any_cast<boost::asio::ip::tcp::resolver::results_type>(second);
                boost::asio::async_connect(
                    sock,
                    results.begin(),
                    results.end(),
                    *this
                );
            }
            std::cout << "async_connect:" << ec.message() << std::endl;
            if (ec) return;

            // write
            yield {
                auto buf = std::make_shared<std::string>("hello");
                boost::asio::async_write(
                    sock,
                    boost::asio::buffer(*buf),
                    boost::asio::consign(
                        *this,
                        buf
                    )
                );
            }
            std::cout << "async_write:"
                      << ec.message()
                      << " bytes transferred:"
                      << std::any_cast<std::size_t>(second)
                      << std::endl;
        }
    }

    boost::asio::ip::tcp::resolver& res;
    boost::asio::ip::tcp::socket& sock;
    boost::asio::coroutine coro;
};

#include <boost/asio/unyield.hpp>

int main() {
    boost::asio::io_context ioc;
    boost::asio::ip::tcp::resolver res{ioc.get_executor()};
    boost::asio::ip::tcp::socket sock{ioc.get_executor()};
    myapp ma{res, sock};
    ma(); // start coroutine
    ioc.run();
}

2. 使用带if constexpr的模板operator()

通过模板参数接收第二个参数,配合if constexpr做类型判断,无需类型转换,但会实例化多个模板版本,协程的switch逻辑可以正常运行:

#include <iostream>
#include <type_traits>

#include <boost/asio.hpp>

#include <boost/asio/yield.hpp>

struct myapp {
    myapp(
        boost::asio::ip::tcp::resolver& res,
        boost::asio::ip::tcp::socket& sock
    ): res{res}, sock{sock}
    {}

    template <typename Second = std::nullptr_t>
    void operator()(
        boost::system::error_code const& ec = boost::system::error_code{},
        Second&& second = nullptr
    ) {
        reenter (coro) {
            // resolve
            yield res.async_resolve("localhost", "1883", *this);
            std::cout << "async_resolve:" << ec.message() << std::endl;
            if (ec) return;

            // connect
            yield {
                if constexpr(
                    std::is_same_v<std::decay_t<Second>, boost::asio::ip::tcp::resolver::results_type>
                ) {
                    boost::asio::async_connect(
                        sock,
                        second.begin(),
                        second.end(),
                        *this
                    );
                }
            }
            std::cout << "async_connect:" << ec.message() << std::endl;
            if (ec) return;

            // write
            yield {
                auto buf = std::make_shared<std::string>("hello");
                boost::asio::async_write(
                    sock,
                    boost::asio::buffer(*buf),
                    boost::asio::consign(
                        *this,
                        buf
                    )
                );
            }
            if constexpr(
                std::is_same_v<std::decay_t<Second>, std::size_t>
            ) {
            std::cout << "async_write:"
                      << ec.message()
                      << " bytes transferred:"
                      << second
                      << std::endl;
            }
        }
    }

    boost::asio::ip::tcp::resolver& res;
    boost::asio::ip::tcp::socket& sock;
    boost::asio::coroutine coro;
};

#include <boost/asio/unyield.hpp>

int main() {
    boost::asio::io_context ioc;
    boost::asio::ip::tcp::resolver res{ioc.get_executor()};
    boost::asio::ip::tcp::socket sock{ioc.get_executor()};
    myapp ma{res, sock};
    ma(); // start coroutine
    ioc.run();
}

3. 编写多个重载operator()

这种方式会破坏无栈协程代码连贯性的核心优势,因此不做考虑。


最佳实现方式分析

对比两种可行方案,带if constexpr的模板operator()是更优的选择,理由如下:

  1. 类型安全:无需std::any_cast的运行时类型转换,完全在编译期完成类型匹配,避免了类型不匹配导致的运行时异常。
  2. 性能更优:编译期处理类型逻辑,没有std::any带来的额外内存开销和运行时转换成本。
  3. 代码可读性:通过if constexpr明确区分不同类型分支,逻辑清晰,后续维护更方便。

另外,还可以进一步优化模板版本的代码,比如将类型判断的逻辑封装成辅助 trait,但基础的if constexpr模板方案已经能很好地解决问题。需要注意的是,模板operator()会根据不同的第二个参数类型实例化多个版本,但这是编译期的开销,不会影响运行时性能,且对于固定的异步操作流程来说,实例化的版本数量是可控的。


内容的提问来源于stack exchange,提问作者Takatoshi Kondo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 23:17:36