我想处理具有相同签名成员函数的
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
它按我的预期工作,但并不优雅。我尝试使用预处理器宏来避免代码重复,但到目前为止我找不到好方法。
环境
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
这里有一个更完整的测试程序:
生活在Coliru或Godbolt
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();
}