What is ASIO `any_io_executor`? Understanding the Polymorphic Executor

asio::any_io_executor is a type-erased polymorphic executor that provides a uniform interface for all I/O objects in the Asio library, enabling generic code to work with any concrete executor without template specialization.

The asio::any_io_executor class in the chriskohlhoff/asio repository serves as the default executor type for sockets, timers, and streams. This polymorphic wrapper abstracts away concrete executor implementations while preserving the essential properties required for asynchronous operations.

What is any_io_executor?

any_io_executor is a polymorphic executor built on top of the Execution library’s execution::any_executor template. It is pre-instantiated with a specific set of properties that every I/O object in Asio requires, living in include/asio/any_io_executor.hpp.

The class inherits from a specialized execution::any_executor configured with seven standard properties:

Property Meaning
execution::context_as_t<execution_context&> Allows querying for the associated execution context.
execution::blocking_t::never_t Guarantees the executor never blocks by default.
execution::prefer_only<execution::blocking_t::possibly_t> Can be preferred to allow blocking operations.
execution::prefer_only<execution::outstanding_work_t::tracked_t> Can be preferred to track outstanding work.
execution::prefer_only<execution::outstanding_work_t::untracked_t> Can be preferred to ignore work tracking.
execution::prefer_only<execution::relationship_t::fork_t> Can be preferred to treat spawned tasks as forks.
execution::prefer_only<execution::relationship_t::continuation_t> Can be preferred to treat spawned tasks as continuations.

According to the source code at lines 37–65, the class definition is:

class any_io_executor :
  public execution::any_executor<
      execution::context_as_t<execution_context&>,
      execution::blocking_t::never_t,
      execution::prefer_only<execution::blocking_t::possibly_t>,
      execution::prefer_only<execution::outstanding_work_t::tracked_t>,
      execution::prefer_only<execution::outstanding_work_t::untracked_t>,
      execution::prefer_only<execution::relationship_t::fork_t>,
      execution::prefer_only<execution::relationship_t::continuation_t>
    >
{
  // ...
};

Implementation Details

When the library is compiled without the legacy TS executor (ASIO_USE_TS_EXECUTOR_AS_DEFAULT), the implementation resides in include/asio/impl/any_io_executor.ipp. This file contains the inline definitions for constructors, assignment operators, swap, and property-specific require/prefer overloads.

The header defines required traits (execute_member, query_member, require_member, prefer_member, equality_comparable) ensuring seamless integration with the Asio execution model.

Key Capabilities

Type Erasure and Uniform Interface

any_io_executor provides type-erasure, allowing it to hold any concrete executor that satisfies the required properties. This hides the concrete type behind a uniform interface, making it the default Executor template parameter for I/O classes like basic_stream_socket.

Flexible Construction

The executor can be constructed from:

  • A concrete executor (any_io_executor exec(my_executor);)
  • Another any_io_executor (copy/move operations)
  • An executor wrapped with std::nothrow_t for no-throw construction guarantees

The constructor implementation appears at lines 90–100 of the header file.

Property Customization

The executor supports customization points via asio::require and asio::prefer, returning a new any_io_executor with modified properties. For example, requesting a possibly-blocking executor returns a new instance with that property applied.

Practical Usage Examples

Constructing from a Concrete Executor

You can wrap any concrete executor, such as one from an io_context, into the polymorphic type:

#include <asio.hpp>

int main() {
  asio::io_context ctx;
  // ctx.get_executor() returns a concrete executor type
  asio::any_io_executor exec{ ctx.get_executor() };   // type-erased wrapper
}

Using with Asynchronous Operations

Most I/O objects default their executor parameter to any_io_executor, enabling generic function signatures:

#include <asio.hpp>

void async_echo(asio::any_io_executor exec) {
  asio::ip::tcp::socket sock(exec);
  // async_read_some uses the stored executor internally
  sock.async_read_some(asio::buffer(data),
      [&](std::error_code ec, std::size_t n) { /* handle completion */ });
}

As implemented in basic_stream_socket.hpp at lines 36–40, the template parameter defaults to any_io_executor.

Modifying Executor Properties

Use asio::require or asio::prefer to obtain an executor with specific characteristics:

#include <asio.hpp>

int main() {
  asio::io_context ctx;
  asio::any_io_executor exec{ ctx.get_executor() };

  // Request a possibly-blocking executor
  auto blk_exec = asio::require(exec, asio::execution::blocking.possibly);
}

The require implementation is located at lines 25–33.

Integration with C++20 Coroutines

Coroutines can accept any_io_executor to remain agnostic of the specific execution context:

#include <asio.hpp>
#include <asio/experimental/coro.hpp>

asio::awaitable<void> coro(asio::any_io_executor exec) {
  asio::ip::tcp::socket sock(exec);
  co_await sock.async_connect(asio::ip::tcp::endpoint{});
}

The coroutine uses whichever executor the caller provides, wrapped in the polymorphic interface.

Summary

  • any_io_executor is a type-erased polymorphic executor built on execution::any_executor with seven standard I/O properties.
  • It resides in include/asio/any_io_executor.hpp with implementations in include/asio/impl/any_io_executor.ipp.
  • It enables type-erasure of concrete executors while supporting property queries via asio::require and asio::prefer.
  • Most I/O objects (sockets, timers, streams) default to any_io_executor, allowing generic code that works with any execution context.
  • It supports construction from concrete executors, copy/move operations, and integration with C++20 coroutines.

Frequently Asked Questions

What is the difference between any_io_executor and concrete executors like io_context::executor_type?

any_io_executor is a type-erased wrapper that can hold any concrete executor satisfying the required properties, while io_context::executor_type is a specific concrete type tied to that execution context. The polymorphic executor allows writing functions that accept any executor without templating, whereas concrete executors provide compile-time type safety and potentially better optimization.

When should I use any_io_executor versus a template parameter?

Use any_io_executor when writing generic code that must accept any executor type without template proliferation, such as library interfaces or coroutine functions. Use template parameters with concrete executor types when you need compile-time optimization and type safety, or when the specific executor type's unique capabilities must be preserved.

Does any_io_executor incur performance overhead compared to concrete executors?

Yes, any_io_executor incurs a small runtime overhead due to type-erasure and virtual dispatch through the execution::any_executor base class. However, this overhead is typically minimal for I/O-bound operations. For CPU-intensive work requiring maximum performance, concrete executor types and templates provide better optimization opportunities.

How do I convert an any_io_executor back to a concrete executor type?

You cannot directly extract the concrete type from an any_io_executor due to type-erasure. If you need the concrete type, you must use asio::query with asio::execution::context to obtain the underlying execution_context, then cast it to the specific context type (e.g., io_context), and finally call get_executor() on that context to retrieve the concrete executor type.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →