How to Use asio as_tuple and as_single for Tuple Completion
asio::as_tuple and asio::experimental::as_single are completion-token adapters that repackage asynchronous operation results into a std::tuple or a single value, respectively, instead of delivering them as separate handler arguments.
In the chriskohlhoff/asio library, these adapters simplify handler signatures by consolidating error codes and return values into a single argument. This approach eliminates the need for separate error and success callbacks, making asynchronous code more compact and easier to compose with standard library utilities.
Understanding as_tuple and as_single
Both adapters work by specializing async_result to intercept native operation arguments and repackage them before forwarding to your handler.
as_tuple: Packing All Arguments into a Tuple
asio::as_tuple wraps any completion token so that all completion arguments are moved into a std::tuple and passed as a single argument to the handler. According to the source code in [include/asio/as_tuple.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/as_tuple.hpp), the class template as_tuple_t is defined at lines 26‑34, while the helper object as_tuple (an instance of partial_as_tuple) is created at lines 45‑48.
When using as_tuple, an operation that normally completes with (std::size_t, std::error_code) instead delivers a std::tuple<std::size_t, std::error_code>.
as_single: Unwrapping Single-Element Tuples
asio::experimental::as_single performs the same repackaging but unwraps a single-element tuple, delivering that element directly to the handler. This is useful when an operation yields exactly one value (such as a result code or scalar).
The implementation resides in [include/asio/experimental/as_single.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/experimental/as_single.hpp) (lines 30‑38 for as_single_t), with the helper object as_single creating an as_single_t instance at lines 119‑122. The actual adaptation logic is in [include/asio/experimental/impl/as_single.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/experimental/impl/as_single.hpp) (lines 33‑41), where as_single_handler and the associated async_result specialization are defined.
Complete Working Examples
The following examples demonstrate how to apply these tokens to asio::async_read operations.
Using as_tuple with use_future
When combined with asio::use_future, as_tuple causes the future to resolve to a tuple containing both the return value and error code:
#include <asio.hpp>
#include <asio/experimental/as_single.hpp>
#include <tuple>
#include <vector>
using asio::ip::tcp;
void example_as_tuple()
{
asio::io_context ctx;
tcp::socket sock(ctx);
std::vector<char> data(1024);
// The async_read will complete with a tuple: (std::size_t, std::error_code)
auto fut = asio::async_read(sock,
asio::buffer(data),
asio::as_tuple(asio::use_future));
// The future resolves to the whole tuple.
std::tuple<std::size_t, std::error_code> result = fut.get();
std::size_t bytes = std::get<0>(result);
std::error_code ec = std::get<1>(result);
// ... handle result ...
}
Using as_single for Single Values
Use as_single when you expect only one value and want to avoid tuple unpacking:
void example_as_single()
{
asio::io_context ctx;
tcp::socket sock(ctx);
std::vector<char> data(512);
// The async_read will complete with a single std::size_t value.
auto fut = asio::async_read(sock,
asio::buffer(data),
asio::experimental::as_single(asio::use_future));
std::size_t bytes = fut.get(); // No tuple – just the size.
// ... handle result ...
}
Setting Default Tokens on I/O Objects
Both adapters support as_default_on, which sets the token as the default for all operations on an I/O object:
void example_default_token()
{
asio::io_context ctx;
tcp::socket sock(ctx);
// Make as_tuple the default completion token for sock.
auto sock_with_tuple = asio::as_tuple::as_default_on(std::move(sock));
std::vector<char> buf(256);
// No explicit token needed – the socket's default token is as_tuple.
sock_with_tuple.async_read_some(
asio::buffer(buf),
[](auto result) { // result is a tuple.
std::size_t n = std::get<0>(result);
std::error_code ec = std::get<1>(result);
// ... use n and ec ...
});
}
Implementation Details
According to the chriskohlhoff/asio source code, these adapters function by providing custom async_result specializations:
as_tuple_handler: Defined in [include/asio/impl/as_tuple.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/impl/as_tuple.hpp), this handler captures all arguments into a tuple before invoking the original completion token.as_single_handler: Defined in [include/asio/experimental/impl/as_single.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/experimental/impl/as_single.hpp), this handler extracts the single element from a tuple result.
Because they are standard completion tokens, you can compose them with any other token including asio::detached, asio::use_future, or custom user-defined tokens.
Summary
asio::as_tupleconsolidates all completion arguments into a singlestd::tuple, making it easier to handle multiple return values (like bytes transferred and error codes) as one unit.asio::experimental::as_singleunwraps single-element tuples, delivering the value directly to the handler when an operation produces exactly one result.- Both adapters are implemented in the chriskohlhoff/asio repository via
as_tuple_tandas_single_tclass templates with specializedasync_resultlogic. - Use
as_default_onto apply either token as the default completion behavior for an I/O object, eliminating the need to specify the token for every operation. - These tokens compose with any other completion token, including futures and detached handlers.
Frequently Asked Questions
How do I choose between as_tuple and as_single?
Use asio::as_tuple when the operation produces multiple arguments (such as a byte count and error code) that you want to handle together. Use asio::experimental::as_single when the operation produces exactly one value and you want to avoid the syntactic overhead of unpacking a single-element tuple.
Can I combine as_tuple with other completion tokens?
Yes. Both as_tuple and as_single are composable completion token adapters. You can wrap them around asio::use_future, asio::detached, or custom tokens. For example, asio::as_tuple(asio::use_future) returns a future that resolves to a tuple.
Where are the implementation files located in the repository?
The core logic is located in [include/asio/as_tuple.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/as_tuple.hpp) and [include/asio/impl/as_tuple.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/impl/as_tuple.hpp) for as_tuple. For as_single, see [include/asio/experimental/as_single.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/experimental/as_single.hpp) and [include/asio/experimental/impl/as_single.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/experimental/impl/as_single.hpp).
Is as_single available in the stable Asio API?
No, as_single currently resides in the asio::experimental namespace, indicating it is part of the experimental API that may change in future releases. The as_tuple adapter is part of the stable API and resides directly in the asio namespace.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →