...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
A completion token adapter is a utility that can be generically applied to a completion token, to produce a new completion token with modified behaviour. Common uses of completion token adapters include:
Boost.Asio includes a number of completion token adapters as described below.
The bind_executor
function adapts a completion token to imbue the completion handler with
an associated executor.
This example shows the bind_executor
adapter applied to a lambda, to specify that the handler should execute
in the specified strand. The arguments to the completion handler are passed
through as-is.
my_socket.async_read_some(my_buffer, boost::asio::bind_executor(my_strand, [](boost::system::error_code error, std::size_t bytes_transferred) { // ... }));
When applied to completion tokens that cause the initiating function to
produce a result, such as use_awaitable
, the result is returned
unmodified.
boost::asio::awaitable<void> my_coroutine() { // ... std::size_t bytes_transferred = co_await my_socket.async_read_some(my_buffer, boost::asio::bind_executor(my_strand, boost::asio::use_awaitable)); // ... }
The bind_allocator
and bind_cancellation_slot
adapters
work similarly, to imbue the completion handler with an associated
allocator or associated
cancellation slot respectively.
The redirect_error
function adapts a completion token to capture the error_code
produced by an operation into a specified variable. In doing so, it modifies
the completion signature to remove the initial error_code
parameter.
This example shows the redirect_error
adapter applied to a lambda, to specify that the error should be captured
into my_error
. The error_code
is no longer passed to the
completion handler, but the remaining arguments are passed through as-is.
boost::system::error_code my_error; // N.B. must be valid until operation completes // ... my_socket.async_read_some(my_buffer, boost::asio::redirect_error( [](std::size_t bytes_transferred) { // ... }, my_error));
When applied to completion tokens that cause the initiating function to
produce a result, such as use_awaitable
, the result is returned
unmodified. However, if the operation fails, the co_await
expression will no longer throw an exception on resumption.
boost::asio::awaitable<void> my_coroutine() { // ... boost::system::error_code my_error; std::size_t bytes_transferred = co_await my_socket.async_read_some(my_buffer, boost::asio::redirect_error(boost::asio::use_awaitable, my_error)); // ... }
The as_tuple
adapter can be used to specify that the completion handler arguments should
be combined into a single tuple argument.
For example, the as_tuple
adapter may be used in conjunction with use_awaitable
and structured bindings
as follows:
auto [e, n] = co_await my_socket.async_read_some(my_buffer, boost::asio::as_tuple(boost::asio::use_awaitable));
This adapter may also be used as a default completion token:
using default_token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<>>; using tcp_socket = default_token::as_default_on_t<tcp::socket>; // ... boost::asio::awaitable<void> do_read(tcp_socket my_socket) { // ... auto [e, n] = co_await my_socket.async_read_some(my_buffer); // ... }
Note | |
---|---|
This is an experimental feature. |
The experimental::as_single
adapter can be used
to specify that the completion handler arguments should be combined into
a single argument. For completion signatures with a single parameter, the
argument is passed through as-is. For signatures with two or more parameters,
the arguments are combined into a tuple.
For example, when applied to a timer wait operation, the single error_code
argument is passed directly
to the completion handler:
my_timer.async_wait( boost::asio::experimental::as_single( [](boost::system::error_code error) { // ... }));
When applied to a socket read operation, where the completion signature specifies two parameters, the handler is passed the result as a tuple:
my_socket.async_read_some(my_buffer, boost::asio::experimental::as_single, [](std::tuple<boost::system::error_code, std::size_t> result) { // ... }));
The append
completion token adapter can be used to pass additional completion handler
arguments at the end of the completion signature.
For example:
timer.async_wait( boost::asio::append( [](boost::system::error_code ec, int i) { // ... }, 42 ) ); std::future<int> f = timer.async_wait( boost::asio::append( boost::asio::use_future, 42 ) );
The prepend
completion token adapter can be used to pass additional completion handler
arguments before the existing completion handler arguments.
For example:
timer.async_wait( boost::asio::prepend( [](int i, boost::system::error_code ec) { // ... }, 42 ) ); std::future<std::tuple<int, boost::system::error_code>> f = timer.async_wait( boost::asio::prepend( boost::asio::use_future, 42 ) );
The consign
completion token adapter can be used to attach additional values to a completion
handler. This is typically used to keep at least one copy of an object,
such as a smart pointer, alive until the completion handler is called.
For example:
auto timer1 = std::make_shared<boost::asio::steady_timer>(my_io_context); timer1->expires_after(std::chrono::seconds(1)); timer1->async_wait( boost::asio::consign( [](boost::system::error_code ec) { // ... }, timer1 ) ); auto timer2 = std::make_shared<boost::asio::steady_timer>(my_io_context); timer2->expires_after(std::chrono::seconds(30)); std::future<void> f = timer2->async_wait( boost::asio::consign( boost::asio::use_future, timer2 ) );
bind_executor, bind_allocator, bind_cancellation_slot, redirect_error, as_tuple, experimental::as_single, append, prepend.