Using Boost.Asio in C++23, how can I declare the below code's endpoints
variable outside of the try
-catch
scope in which it is assigned a value? If it was declared inside the try
, the assignment would become initialization, hence its type could be deduced via auto
. However, having the declaration outside the try
separates the declaration from the desired value, hence auto
cannot be used. The desired value in this case comes from co_await
ing resolver.async_resolve()
.
One thing I like about c++ is the predictability of the return types. However with asio, looking at the source code, I cannot figure it out. In this particular case, a specific answer would be helpful, but I would also appreciate recommendations how to do figure out actual types based on the given context, with asio or other heavy template based libraries that deduce the return type at compile time.
boost::beast::error_code ec;
// How to put `endpoints` to scope outside try-catch?
// auto endpoints = ...?
try {
// Before
// auto const endpoints = co_await resolver.async_resolve(host, port);
// After
endpoints = co_await resolver.async_resolve(host, port);
}
catch (const std::exception& e) {
ec = error_code(asio::error::host_not_found);
co_return ec;
}
// use `endpoints` without putting the code into the `try` block
auto endpoint = co_await beast::get_lowest_layer(stream).async_connect(endpoints, asio::redirect_error(asio::use_awaitable, ec));
You can use the decltype
trick on the awaitable<>
:
using T = decltype(resolver.async_resolve(host, port, asio::use_awaitable))::value_type;
T endpoints;
Of course, you likely know the exact type of resolver
so you can simply use that knowledge:
auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex);
beast::tcp_stream stream(ex);
tcp::resolver::results_type endpoints;
However, you could greatly simplify. You already use ec
. Just go all the way?
auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex);
beast::tcp_stream stream(ex);
beast::error_code ec;
auto token = asio::redirect_error(ec);
auto endpoints = co_await resolver.async_resolve(host, port, token);
if (ec)
co_return asio::error::host_not_found; // co_return ec; ?
auto endpoint = co_await get_lowest_layer(stream).async_connect(endpoints, token);
if (!ec)
std::cout << "Connected to " << endpoint << std::endl;
Of course, I'd personally always favor exception transparency:
asio::awaitable<boost::beast::error_code> run_server(std::string host, std::string port) try {
auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex);
auto eps = co_await resolver.async_resolve(host, port);
beast::tcp_stream stream(ex);
auto ep = co_await get_lowest_layer(stream).async_connect(eps);
std::cout << "Connected to " << ep << std::endl;
} catch (beast::system_error const& se) {
co_return se.code();
}
Or, even more to the point, exception neutrality:
asio::awaitable<void> run_server(std::string host, std::string port) {
auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex);
auto eps = co_await resolver.async_resolve(host, port);
beast::tcp_stream stream(ex);
auto ep = co_await get_lowest_layer(stream).async_connect(eps);
std::cout << "Connected to " << ep << std::endl;
}