# How to Use asio as_tuple and as_single for Tuple Completion

> Learn how to use asio as_tuple and as_single to simplify asynchronous operation results into tuples or single values for cleaner code and easier handling.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: how-to-guide
- Published: 2026-07-17

---

**`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](https://github.com/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/main/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/main/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/main/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:

```cpp
#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:

```cpp
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:

```cpp
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/main/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/main/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_tuple`** consolidates all completion arguments into a single `std::tuple`, making it easier to handle multiple return values (like bytes transferred and error codes) as one unit.
- **`asio::experimental::as_single`** unwraps 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_t` and `as_single_t` class templates with specialized `async_result` logic.
- Use **`as_default_on`** to 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/main/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/main/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/main/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/main/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.