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_tfor 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_executoris a type-erased polymorphic executor built onexecution::any_executorwith seven standard I/O properties.- It resides in
include/asio/any_io_executor.hppwith implementations ininclude/asio/impl/any_io_executor.ipp. - It enables type-erasure of concrete executors while supporting property queries via
asio::requireandasio::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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →