我基于Boost.Asio编写了一些名为
client
的网络客户端类。 client
在内部使用 boost::asio::async_read()
来读取字节直到期望的字节。
当我将默认完成标记支持的代码添加到client
并使用默认标记时,编译器会报告错误call to 'async_read' is ambiguous
。
两个不明确的函数在这里:
https://github.com/boostorg/asio/blob/boost-1.84.0/include/boost/asio/impl/read.hpp#L505 https://github.com/boostorg/asio/blob/boost-1.84.0/include/boost/asio/impl/read.hpp#L525
Linux,x86-64 clang 18.1.0,-std=c++20 升压1.84.0
注意:这仅用于检查编译错误。
godbolt 运行演示:https://godbolt.org/z/b344jdWzh
#include <string>
#include <boost/asio.hpp>
namespace as = boost::asio;
std::string str;
template <typename NextLayer>
struct client {
// type aliases
using next_layer_type = NextLayer;
using executor_type = typename next_layer_type::executor_type;
// constructor
template <typename... Args>
explicit client(Args&&... args):nl{std::forward<Args>(args)...}{}
// rebind constructor for default token
template <typename Other>
explicit client(client<Other>&& other):nl{std::move(other.nl)} {}
// accessor
next_layer_type const& next_layer() const { return nl; };
next_layer_type& next_layer() { return nl; };
executor_type get_executor() { return nl.get_executor(); }
// async_func
template <
typename CompletionToken = as::default_completion_token_t<executor_type>
>
auto async_read_packet(
CompletionToken&& token = as::default_completion_token_t<executor_type>{}
) {
return as::async_compose<
CompletionToken,
void(boost::system::error_code, std::size_t)
>( read_packet_op{ *this }, token );
}
// async_func impl
struct read_packet_op {
client& c;
template <typename Self>
void operator()(Self& self) {
#if 1
// calling free function causes error: call to 'async_read' is ambiguous
as::async_read(
c.next_layer(),
as::buffer(str),
std::move(self)
);
#else
// calling member function, no error
c.next_layer().async_read_some(
as::buffer(str),
std::move(self)
);
#endif
}
template <typename Self>
void operator()(Self& self, boost::system::error_code ec, std::size_t size) {
self.complete(ec, size);
}
};
// rebind for default token
template <typename Executor1>
struct rebind_executor {
using other = client<
typename NextLayer::template rebind_executor<Executor1>::other
>;
};
// member variables
next_layer_type nl;
};
as::awaitable<void> coro_test(auto& c) {
auto size = co_await c.async_read_packet(as::use_awaitable);
(void)size;
}
as::awaitable<void> coro_test_default(auto& c) {
auto size = co_await c.async_read_packet();
(void)size;
}
int main() {
using tcp = as::basic_stream_socket<as::ip::tcp, as::any_io_executor>;
as::io_context ioc;
{
// no default token version
client<tcp> c{ioc.get_executor()};
as::co_spawn(
c.get_executor(),
coro_test(c),
as::detached
);
}
{
// default token version
using default_token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<>>;
using def_client = default_token::as_default_on_t<client<tcp>>;
def_client c{ioc.get_executor()};
// auto c{as::use_awaitable.as_default_on(client<tcp>{ioc.get_executor()})};
as::co_spawn(
c.get_executor(),
coro_test_default(c),
as::detached
);
}
ioc.run();
}
我用成员函数
async_read()
替换了免费函数版本的async_read_some()
。然后就不会出现编译错误了。
将
#if 1
替换为 #if 0
即可看到结果
template <typename Self>
void operator()(Self& self) {
#if 1
// calling free function causes error: call to 'async_read' is ambiguous
as::async_read(
c.next_layer(),
as::buffer(str),
std::move(self)
);
#else
// calling member function, no error
c.next_layer().async_read_some(
as::buffer(str),
std::move(self)
);
#endif
}
所以我猜这是一些与自由功能相关的问题。 有什么办法可以解决吗
是的,这是一个问题。我也偶尔遇到过。它与“自由功能”无关。相反,它与其他重载的存在有关,当让编译器推断默认的完成标记时,这些重载变得不明确。
注意:
的默认值在前向声明头ReadToken
中声明,而不是编译器报告的定义位置,asio/read.hpp
asio/impl/read.hpp
在大多数情况下,我已经能够通过明确说明令牌来消除歧义:
asio::async_read(s, b, asio::transfer_all(), asio::deferred);
在通用代码中,您可以通过将 Token 扣除与重载决策分开来实现:
using Token = asio::default_completion_token_t<decltype(s)::executor_type>;
asio::async_read(s, b, asio::transfer_all(), Token());
这是一个最小化的示例:https://godbolt.org/z/36zjdbj8x
也许这个最小的例子有点太小了。其他操作似乎遇到相反的情况:显式指定令牌会使重载解析失败,为此目的请考虑以下也练习
asio::async_connect
的示例:
#include <boost/asio.hpp>
#include <boost/core/ignore_unused.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
int main() {
auto x = asio::system_executor{};
auto r = asio::deferred.as_default_on(tcp::resolver{x});
auto s = asio::deferred.as_default_on(tcp::socket{x});
asio::streambuf b;
using Token = asio::default_completion_token_t<decltype(s)::executor_type>;
// auto op1 = asio::async_connect(s, r.resolve("", "8989"), Token());// BROKEN
auto op1 = asio::async_connect(s, r.resolve("", "8989")); // Workaround
// but conversely:
// auto op2 = asio::async_read(s, b, asio::transfer_all()); // BROKEN
auto op2 = asio::async_read(s, b, asio::transfer_all(), Token()); // Workaround
boost::ignore_unused(op1, op2);
}