有什么优雅的方式来结合 asio::co_composed 和 std::variant 吗?

问题描述 投票:0回答:1

问题

我想处理具有相同签名成员函数的

connection
类。例如,
tcp
tls
都是
connection
类。他们有支持
send()
CompletionToken
成员函数模板。 我使用
std::variant
来表示
connection
类型。

在我的应用程序的某些部分,我定义了

non_awaitable_func
函数模板。它支持
CompletionToken
,我想使用
co_await
来实现它。在这种情况下,我们可以使用
asio::experimental::co_composed

到目前为止,一切都很好。

但是,当我使用

connection
调用
send()
boost::asio::deferred
成员函数模板时,断言失败了。 这是因为
std::visit
访问者函数的返回类型不同。
std::visit
可以创建不同的返回类型,可以转换为给定的签名。 通常,在这种情况下,我使用
boost::asio::deferred
而不是
boost::asio::use_awaitable
boost::asio::deferred
创建相同的返回类型。但是,在
boost::asio::use_awaitable
实现中,我无法使用
co_composed
有什么好的办法可以解决这种情况吗?

演示代码

boost::asio::use_awaitable

godbolt 链接:
https://godbolt.org/z/b7zac6Pn1

我的解决方法

我将

#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) { // pseudo implementation 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) { // pseudo implementation 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 // if set 0 then no error happens because all visit return types are the same 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) { // use_awaitable can't be used here because of co_composed return c.send(boost::asio::as_tuple(boost::asio::deferred)); }, con ); // user defined implementation 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::visit
std::get_if

godbolt 链接:
https://godbolt.org/z/z8YMfMvMv

它按我的预期工作,但并不优雅。我尝试使用预处理器宏来避免代码重复,但到目前为止我找不到好方法。

环境

增强1.84.0
  • clang++ 17.0.1
  • 编译器选项-std=c++20
c++ boost c++20 coroutine asio
1个回答
0
投票
boost::asio::experimental::co_composed< void(boost::system::error_code) >( [](auto /*state*/, connection& con) -> void { // No std::visit approach // However, not elegant and appears similar code repeatedly... if (auto* p = std::get_if<0>(&con)) { auto [ec] = co_await p->send(boost::asio::as_tuple(boost::asio::deferred)); // user defined implementation co_return {ec}; } else { // p conflicts auto* q = std::get_if<1>(&con); BOOST_ASSERT(q); auto [ec] = co_await q->send(boost::asio::as_tuple(boost::asio::deferred)); // user defined implementation co_return {ec}; } } ),

两者都不是没有开销的,所以也许你可以将访问翻转过来,这样组合操作就永远不会变:

co_composed

这有效。如果您不介意可读性,您可以将两者结合起来:

template <typename Connection, typename CompletionToken> auto non_awaitable_func_impl(Connection& con, CompletionToken&& token) { return asio::async_initiate<CompletionToken, Sig>( asio::experimental::co_composed<Sig>([](auto state, Connection& con) -> void { auto [ec] = co_await con.send(as_tuple(asio::deferred)); co_yield state.complete(ec); }), token, con); } template <typename CompletionToken> auto non_awaitable_func(connection& con, CompletionToken&& token) { std::visit( [&token](auto& con) { return non_awaitable_func_impl(con, std::forward<CompletionToken>(token)); }, con); }

看到它

住在Coliru(或Godbolt template <typename CompletionToken> auto non_awaitable_func(connection& con, CompletionToken&& token) { return std::visit( [&token](auto& con) { return asio::async_initiate<CompletionToken, Sig>( asio::experimental::co_composed<Sig>([&con](auto state) -> void { auto [ec] = co_await con.send(as_tuple(asio::deferred)); co_yield state.complete(ec); }), token); }, con); }

请注意,让 ADL 找到正确的 
#pragma GCC diagnostic ignored "-Wmismatched-new-delete" #include <chrono> #include <iostream> #include <boost/asio.hpp> #include <boost/asio/experimental/co_composed.hpp> using namespace std::chrono_literals; namespace asio = boost::asio; using error_code = boost::system::error_code; using Sig = void(error_code); struct tcp { template <asio::completion_token_for<Sig> Token> auto send(Token&& token) { // pseudo implementation auto tim = std::make_unique<asio::steady_timer>(exe_, 1s); return tim->async_wait(consign(std::forward<Token>(token), std::move(tim))); } asio::any_io_executor exe_; }; struct tls { template <asio::completion_token_for<Sig> Token> auto send(Token&& token) { return dispatch( // pseudo implementation append(std::forward<Token>(token), make_error_code(boost::system::errc::bad_message))); } asio::any_io_executor exe_; }; using connection = std::variant<tcp, tls>; template <asio::completion_token_for<Sig> Token> auto non_awaitable_func(connection& con, Token&& token) { return std::visit( [&token](auto& con) { return asio::async_initiate<Token, Sig>( asio::experimental::co_composed<Sig>([&con](auto state) -> void { auto [ec] = co_await con.send(as_tuple(asio::deferred)); co_return state.complete(ec); }), token); }, con); } int main() { asio::io_context ioc; connection con1 = tls{ioc.get_executor()}, con2 = tcp{ioc.get_executor()}; non_awaitable_func(con1, [&](error_code ec) { std::cout << "cb1:" << ec.message() << std::endl; }); non_awaitable_func(con2, [&](error_code ec) { std::cout << "cb2:" << ec.message() << std::endl; }); ioc.run(); }

过载非常重要。

打印:

make_error_code

更新:承诺!

我灵机一动。另一种实验类型,

cb1:Bad message cb2:Success

,与

asio::experimental::promise<>
一样,显然在内部进行了某种类型擦除,但与
std::promise
不同,它也可以在 Asio 协程中进行
std::future
转换。
确实有效:

await

这里有一个更完整的测试程序:

生活在ColiruGodbolt template <asio::completion_token_for<Sig> Token> // auto async_send(connection& con, Token&& token) { return asio::async_initiate<Token, Sig>( boost::asio::experimental::co_composed<Sig>([&con](auto /*state*/) -> void { auto [ec] = co_await std::visit( [](auto& c) { return c.send(asio::as_tuple(asio::experimental::use_promise)); }, con); co_return {ec}; }), token); }

打印例如

#pragma GCC diagnostic ignored "-Wmismatched-new-delete" #include <boost/asio.hpp> #include <boost/asio/experimental/co_composed.hpp> #include <boost/asio/experimental/promise.hpp> #include <boost/asio/experimental/use_coro.hpp> #include <boost/asio/experimental/use_promise.hpp> #include <boost/core/demangle.hpp> #include <chrono> #include <iostream> #include <syncstream> using namespace std::chrono_literals; namespace asio = boost::asio; using error_code = boost::system::error_code; using Sig = void(error_code); static inline auto out() { return std::osyncstream(std::clog); } struct tcp { template <asio::completion_token_for<Sig> Token> // auto send(Token&& token) { // pseudo implementation auto tim = std::make_unique<asio::steady_timer>(exe_, 1s); return tim->async_wait(consign(std::forward<Token>(token), std::move(tim))); } asio::any_io_executor exe_; }; struct tls { template <asio::completion_token_for<Sig> Token> // auto send(Token&& token) { return dispatch( // pseudo implementation append(std::forward<Token>(token), make_error_code(boost::system::errc::bad_message))); } asio::any_io_executor exe_; }; using connection = std::variant<tcp, tls>; template <asio::completion_token_for<Sig> Token> // auto async_send(connection& con, Token&& token) { return asio::async_initiate<Token, Sig>( boost::asio::experimental::co_composed<Sig>([&con](auto /*state*/) -> void { auto [ec] = co_await std::visit( [](auto& c) { return c.send(asio::as_tuple(asio::experimental::use_promise)); }, con); co_return {ec}; }), token); } template <class V> // HT: https://stackoverflow.com/a/53697591/85371 std::type_info const& var_type(V const& v) { return std::visit([](auto&& x) -> decltype(auto) { return typeid(x); }, v); } int main() { asio::thread_pool ioc(1); connection con1 = tls{ioc.get_executor()}, con2 = tcp{ioc.get_executor()}; { // callback async_send(con1, [&](error_code ec) { out() << "cb1:" << ec.message() << std::endl; }); async_send(con2, [&](error_code ec) { out() << "cb2:" << ec.message() << std::endl; }); } { // use_future auto f1 = async_send(con1, as_tuple(asio::use_future)); auto f2 = async_send(con2, as_tuple(asio::use_future)); out() << "f1: " << std::get<0>(f1.get()).message() << std::endl; out() << "f2: " << std::get<0>(f2.get()).message() << std::endl; try { async_send(con1, asio::use_future).get(); } catch (boost::system::system_error const& se) { out() << "alternatively: " << se.code().message() << std::endl; } } { // use_awaitable for (connection& con : {std::ref(con1), std::ref(con2)}) { auto name = "coro-" + boost::core::demangle(var_type(con).name()); co_spawn( ioc, [&con, name]() -> asio::awaitable<void> { auto [ec_defer] = co_await async_send(con, as_tuple(asio::deferred)); auto [ec_aw] = co_await async_send(con, as_tuple(asio::use_awaitable)); out() << name << ": " << ec_defer.message() << "/" << ec_aw.message() << std::endl; co_await async_send(con, asio::deferred); // will throw }, [name](std::exception_ptr e) { try { if (e) std::rethrow_exception(e); } catch (boost::system::system_error const& se) { out() << name << " threw " << se.code().message() << std::endl; } }); } } ioc.join(); }

© www.soinside.com 2019 - 2024. All rights reserved.