I want to write a custom completion token for asio, like asio::experimental::use_tuple, but which actually returns a tl::expected.
The usage would be like:
asio::awaitable<void> echo(tcp::socket socket) {
char data[1024];
for (;;) {
tl::expected<std::size_t> result = co_await socket.async_read_some(
asio::buffer(data), as_expected(asio::use_awaitable));
if (result.has_value()) {
// success
}
// ...
}
}
I'm following Richard Hodges's article about creating a custom timeout completion token to get some of the boilerplate right but am running into trouble.
I'm stuck on writing the initiation function object. How do I call the underlying Initiation correctly, transform the result of the completion handler, and give it my wrapped CompletionHandler?
Here's what I have so far (expected_initiation is where I am stuck):
// token type
template <typename CompletionToken>
struct expected_token {
CompletionToken& token;
};
// initiation function
template <typename... Signatures>
struct expected_initiation {
template <typename CompletionHandler, typename Initiation,
typename... InitArgs>
void operator()(CompletionHandler handler, std::chrono::milliseconds timeout,
Initiation&& initiation, InitArgs&&... init_args) {
auto ex = asio::get_associated_executor(
handler, asio::get_associated_executor(initiation));
// is this bind_executor right? how do I transform the output and call
// handler()?
asio::bind_executor(ex, [&](auto&& token) {
return asio::async_initiate<decltype(token), Signatures...>(
std::forward<Initiation>(initiation), token,
std::forward<InitArgs>(init_args)...);
})
}
};
// specialization
template <typename InnerCompletionToken, typename... Signatures>
struct asio::async_result<expected_token<InnerCompletionToken>, Signatures...> {
template <typename Initiation, typename... InitArgs>
static auto initiate(Initiation&& init,
expected_token<InnerCompletionToken> t,
InitArgs&&... init_args) {
return asio::async_initiate<InnerCompletionToken, Signatures...>(
expected_initiation<Signatures...>{}, t.token,
std::forward<Initiation>(init), std::forward<InitArgs>(init_args)...)
}
};
template <typename CompletionToken>
auto as_expected(CompletionToken&& token) {
return expected_token<CompletionToken>{token};
}
I'm worried I'm on the wrong path. I tried to look at the source of experimental::as_tuple, but it was beyond me and looked much more complicated than my attempt, so I wonder if Richard's article is less applicable than I thought, since I'm modifying the completion handler's signature with my token.
Edit: There's a lot more to this than I thought. To transform the completion signature like this, I think I need to do something like use_future does and specialize for a bunch of signatures:
// Helper template to choose the appropriate concrete promise handler
// implementation based on the supplied completion signature.
template <typename> class promise_handler_selector;
template <>
class promise_handler_selector<void()>
: public promise_handler_0 {};
template <>
class promise_handler_selector<void(asio::error_code)>
: public promise_handler_ec_0 {};
template <>
class promise_handler_selector<void(std::exception_ptr)>
: public promise_handler_ex_0 {};
template <typename Arg>
class promise_handler_selector<void(Arg)>
: public promise_handler_1<Arg> {};
template <typename Arg>
class promise_handler_selector<void(asio::error_code, Arg)>
: public promise_handler_ec_1<Arg> {};
template <typename Arg>
class promise_handler_selector<void(std::exception_ptr, Arg)>
: public promise_handler_ex_1<Arg> {};
#if defined(ASIO_HAS_VARIADIC_TEMPLATES)
template <typename... Arg>
class promise_handler_selector<void(Arg...)>
: public promise_handler_n<std::tuple<Arg...> > {};
template <typename... Arg>
class promise_handler_selector<void(asio::error_code, Arg...)>
: public promise_handler_ec_n<std::tuple<Arg...> > {};
template <typename... Arg>
class promise_handler_selector<void(std::exception_ptr, Arg...)>
: public promise_handler_ex_n<std::tuple<Arg...> > {};
This may be well over my head.