如何优雅结合asio::co_composed与std::variant解决异步调用问题
我需要处理拥有相同签名成员函数的connection类,比如tcp和tls都属于connection类,二者都提供支持CompletionToken的send()成员函数模板。我用std::variant来管理connection类型。
在应用的某部分,我定义了支持CompletionToken的non_awaitable_func函数模板,希望通过co_await实现它,这种场景可以用asio::experimental::co_composed。
到目前为止一切正常。
但当我用std::visit调用connection的send()成员函数模板(传入boost::asio::deferred)时,出现断言失败。原因是std::visit的访问器函数返回类型不同:boost::asio::deferred会生成可转换为指定签名的不同返回类型。
通常这种场景我会用boost::asio::use_awaitable替代boost::asio::deferred,因为boost::asio::use_awaitable会生成统一的返回类型,但在co_composed的实现里没法用它。
有没有更好的解决方法?
演示代码
#include <iostream> #include <chrono> #include <boost/asio.hpp> #include <boost/asio/experimental/co_composed.hpp> struct tcp { template <typename CompletionToken> // void(boost::system::error_code) auto send(CompletionToken&& token) { // 伪实现 auto tim = std::make_shared<boost::asio::steady_timer>(exe_, std::chrono::seconds(1)); return tim->async_wait( boost::asio::consign( std::forward<CompletionToken>(token), tim ) ); } boost::asio::any_io_executor exe_; }; struct tls { template <typename CompletionToken> // void(boost::system::error_code) auto send(CompletionToken&& token) { // 伪实现 return boost::asio::dispatch( boost::asio::append( std::forward<CompletionToken>(token), boost::system::errc::make_error_code(boost::system::errc::bad_message) ) ); } boost::asio::any_io_executor exe_; }; #if 0 // 设为0时无错误,因为所有visit返回类型相同 using connection = std::variant<tcp, tls>; #else using connection = std::variant<tcp>; #endif template <typename CompletionToken> auto non_awaitable_func( connection& con, CompletionToken&& token ) { return boost::asio::async_initiate< CompletionToken, void(boost::system::error_code) >( boost::asio::experimental::co_composed< void(boost::system::error_code) >( [](auto /*state*/, connection& con) -> void { auto [ec] = co_await std::visit( [](auto& c) { // co_composed中无法使用use_awaitable return c.send(boost::asio::as_tuple(boost::asio::deferred)); }, con ); // 用户自定义实现 co_return {ec}; } ), token, con ); } int main() { boost::asio::io_context ioc; connection con = tcp{ioc.get_executor()}; non_awaitable_func( con, [&] (boost::system::error_code ec) { std::cout << "cb called:" << ec << std::endl; } ); ioc.run(); }
临时解决方案
我用std::get_if替代了std::visit。
boost::asio::experimental::co_composed< void(boost::system::error_code) >( [](auto /*state*/, connection& con) -> void { // 不使用std::visit的方案 // 但不够优雅,存在重复代码... if (auto* p = std::get_if<0>(&con)) { auto [ec] = co_await p->send(boost::asio::as_tuple(boost::asio::deferred)); // 用户自定义实现 co_return {ec}; } else { // p冲突 auto* q = std::get_if<1>(&con); BOOST_ASSERT(q); auto [ec] = co_await q->send(boost::asio::as_tuple(boost::asio::deferred)); // 用户自定义实现 co_return {ec}; } } ),
该方案能正常工作,但不够优雅。我尝试用预处理器宏避免代码重复,但目前还没找到合适的方法。
- Boost 1.84.0
- clang++ 17.0.1
- 编译选项:-std=c++20
我注意到non_awaitable_func()的名称容易引起误解,特此澄清:它指返回类型非boost::asio::awaitable<T>的异步函数,该函数带有CompletionToken参数,支持各类Boost.Asio的CompletionToken类型,例如use_future、use_awaitable和deferred。
内容的提问来源于stack exchange,提问作者Takatoshi Kondo

