如何检查有效的boost asio CompletionToken?



我在写Boost。Asio风格的async函数,接受CompletionToken参数。参数可以是函数、函数对象、lambda表达式、future、awaitable等。

CompletionToken为模板参数。如果我不限制参数,函数可以匹配意外的参数,如int。所以我想用std::enable_if写一些限制。(我的环境是c++ 17)。

如果CompletionToken有参数,那么我可以使用std::is_invocable进行检查。

参见我的示例代码中的f1

然而,我遇到了一个问题。如果CompletionToken不带参数,则boost::asio::use_future出错。

参见示例代码中的f2

经过一些尝试和错误,我得到了我的解决方案。这是连接std::is_invocableuse_future_t检查OR (||)。

参见我的示例代码中的f3

但它不是那么优雅。此外,我不确定Boost支持的其他功能。use_awaitable_t也需要类似的直接匹配检查。

我试着找到Boost。Asio提供了类型特征或谓词,如is_completion_token,但我找不到它。

是否有更好的方法来检查CompletionToken?

Godbolt link https://godbolt.org/z/sPeMo1GEK

完整代码:

#include <type_traits>
#include <boost/asio.hpp>
// Callable T takes one argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T, int>>* = nullptr
>
void f1(T) {
}
// Callable T takes no argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T>>* = nullptr
>
void f2(T) {
}

template <template <typename...> typename, typename>
struct is_instance_of : std::false_type {};
template <template <typename...> typename T, typename U>
struct is_instance_of<T, T<U>> : std::true_type {};
// Callable T takes no argument
template <
typename T,
std::enable_if_t<
std::is_invocable_v<T> ||
is_instance_of<boost::asio::use_future_t, T>::value
>* = nullptr
>
void f3(T) {
}
int main() {
// no error
f1([](int){});
f1(boost::asio::use_future);
// same rule as f1 but use_future got compile error
f2([](){});
f2(boost::asio::use_future); // error
// a little complecated typechecking, then no error
f3([](){});
f3(boost::asio::use_future);
}

输出:

Output of x86-64 clang 13.0.1 (Compiler #1)
<source>:45:5: error: no matching function for call to 'f2'
f2(boost::asio::use_future); // error
^~
<source>:17:6: note: candidate template ignored: requirement 'std::is_invocable_v<boost::asio::use_future_t<std::allocator<void>>>' was not satisfied [with T = boost::asio::use_future_t<>]
void f2(T) {
^
1 error generated.

如果你有c++20的概念,请看下面。否则,请继续阅读

当您想要使用Asio正确实现异步结果协议时,您可以使用async_resulttrait,或者此处记录的async_initiate

这应该是SFINAE的可靠密钥。async_result的模板参数包括令牌完成签名:

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <iostream>
using boost::asio::async_result;
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void(int)>::return_type>
void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "n";
}
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void()>::return_type>
void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "n";
}
int main() {
auto cb1 = [](int) {};
f1(cb1);
f1(boost::asio::use_future);
f1(boost::asio::use_awaitable);
f1(boost::asio::detached);
f1(boost::asio::as_tuple(boost::asio::use_awaitable));
auto cb2 = []() {};
f2(cb2);
f2(boost::asio::use_future);
f2(boost::asio::use_awaitable);
f2(boost::asio::detached);
f2(boost::asio::as_tuple(boost::asio::use_awaitable));
}

已经打印

void f1(Token&&) [with Token = main()::<lambda(int)>&; R = void]
void f1(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<int>]
void f1(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<int, boost::asio::any_io_executor>]
void f1(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f1(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<int>, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = main()::<lambda()>&; R = void]
void f2(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<void>]
void f2(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<void, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f2(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<>, boost::asio::any_io_executor>]

C + + 20概念现在请记住,上面是"太轻了"。由于部分模板实例化。async_result的某些部分实际上没有使用。这意味着f2(cb1);实际上可以编译。

链接的文档甚至包括c++ 20completion_token_for<Sig>概念,允许您毫不费力地精确:Live On Compiler Explorer

template <boost::asio::completion_token_for<void(int)> Token> void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "n";
}
template <boost::asio::completion_token_for<void()> Token> void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "n";
}
否则

在实践中,您将始终遵循Asio配方,这保证了所有部分都被使用。除了文档中的示例,您还可以搜索现有的答案

的例子:

template <typename Token>
typename asio::async_result<std::decay_t<Token>, void(error_code, int)>::return_type
async_f1(Token&& token) {
auto init = [](auto completion) {
auto timer =
std::make_shared<asio::steady_timer>(boost::asio::system_executor{}, 1s);
std::thread(
[timer](auto completion) {
error_code ec;
timer->wait(ec);
std::move(completion)(ec, 42);
},
std::move(completion))
.detach();
};
return asio::async_result<std::decay_t<Token>, void(error_code, int)>::initiate(
init, std::forward<Token>(token));
}

最新更新