What's going on with asio::async_compose?
The following code snippet produces a compilation error: error: deduced type 'void' for 'err' is incomplete.
What I don't understand is, shouldn't errbe of type error_code?
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace asio = boost::asio;
namespace sys = boost::system;
template
void debugln(std::FILE* stream, std::format_string fmt, Args&&... args)
{
std::string s = std::format(fmt, std::forward(args)...);
std::fprintf(stream, "%s\n", s.c_str());
}
template
void debugln(std::format_string fmt, Args&&... args)
{
debugln(stdout, fmt, std::forward(args)...);
}
namespace detail {
struct connect_op
{
std::string_view host_;
std::string_view port_;
asio::ip::tcp::socket* sock_;
asio::ip::tcp::resolver resolv_;
connect_op(std::string_view host, std::string_view port, asio::ip::tcp::socket* tcp_sock)
: resolv_(tcp_sock->get_executor())
{
host_ = host;
port_ = port;
sock_ = tcp_sock;
}
// This overload will be called after the async_resolve completes
template
void operator()(Self& self, sys::error_code ec, asio::ip::tcp::resolver::results_type endpoints)
{
if (ec) {
debugln("(connect_op) async_resolve failed: {}", ec.message());
return self.complete(ec);
} else {
debugln("(connect_op) async_resolve succeeded: {}", endpoints.size());
asio::async_connect(*sock_, std::move(endpoints), std::move(self));
}
}
// This overload will be called after the async_connect completes
template
void operator()(
Self& self,
sys::error_code ec,
const asio::ip::tcp::endpoint& selected_endpoint
)
{
if (ec) {
debugln("(connect_op) async_connect failed: {}", ec.message());
} else {
debugln("(connect_op) async connected to {}", selected_endpoint.address().to_string());
}
self.complete(ec);
}
// This overload will be used for the initiation completion handler
// `async_compose` will cause the implementation (in this case, `connect_op`) to be called once
// with no arguments (other than self) during initiation
template
void operator()(Self& self, sys::error_code ec = {})
{
boost::ignore_unused(ec);
resolv_.async_resolve(host_, port_, std::move(self));
}
};
} // namespace detail
class tcp_connection
{
public:
/// Executor type.
using executor_type = asio::any_io_executor;
tcp_connection(std::string_view host, std::string_view port, executor_type ex)
: host_(host), port_(port), sock_(ex, asio::ip::tcp::v4())
{
}
tcp_connection(std::string_view host, std::string_view port, asio::io_context& ioc)
: tcp_connection(host, port, ioc.get_executor())
{
}
/// Returns the underlying executor.
executor_type get_executor() noexcept { return sock_.get_executor(); }
template
auto async_connect(CompletionToken&& token = {})
{
return asio::async_compose(
detail::connect_op(host_, port_, &sock_),
token,
sock_.get_executor()
);
}
private:
std::string host_;
std::string port_;
asio::ip::tcp::socket sock_;
};
asio::awaitable co_main(std::string_view host, std::string_view port)
{
auto exector = co_await asio::this_coro::executor;
auto conn = tcp_connection(host, port, exector);
// ERROR: deduced type 'void' for 'err' is incomplete
// why err is void?
auto err = co_await conn.async_connect();
co_return;
}
int main(int argc, char* argv[])
{
if (argc < 3) {
std::printf("Usage: %s \n", argv[0]);
return 1;
}
std::string host = argv[1];
std::string port = argv[2];
try {
asio::io_context ioc;
asio::co_spawn(ioc, co_main(host, port), [](std::exception_ptr p) {
if (p) {
std::rethrow_exception(p);
}
});
ioc.run();
return 0;
} catch (std::exception const& e) {
debugln("(main) {}", e.what());
return 1;
}
}