我在写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_invocable
和use_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_result
trait,或者此处记录的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));
}