C++26 standardizes senders and schedulers for lazy task graphs. Build a high-throughput pipeline with practical code. Read now.
What C++26 executors actually give you
C++26 standardizes senders and schedulers so concurrency is expressed as a lazy task graph rather than as ad hoc threads, queues, and callbacks. A sender describes work that will produce a value, an error, or a stop signal. A scheduler decides where and when that work runs. Connecting them builds a pipeline you can compose, cancel, and schedule without starting any execution until you ask for it.
That lazy model is the heart of the “zero-cost” claim. Composition is a compile-time graph of types and operations. When you keep the graph concrete and avoid type erasure at hot boundaries, the optimizer can inline and fuse stages the same way it would fuse ordinary function calls. You pay for concurrency when work actually runs on a scheduler, not for the act of describing the pipeline.
Compared with future-based or callback-driven designs, senders push completion channels into the type system: success, failure, and cancellation are first-class paths. That makes error handling and stop requests part of the graph instead of side conventions every team reinvents.
Sketch a high-throughput pipeline
Think of a pipeline as stages that transform or route work: ingest, parse, validate, enrich, and sink. With senders, each stage is a small operation that receives input and returns a new sender. You chain them so the whole path remains one composable unit. Fan-out and fan-in become explicit graph edges rather than hidden thread pools with shared mutable queues.
A practical shape looks like this:
- A producer sender that yields batches of work units.
- CPU-bound stages scheduled on a compute scheduler.
- I/O-bound stages scheduled on an I/O-friendly scheduler.
- A sink that commits results and surfaces errors without tearing down the rest of the graph.
Keep batch size and backpressure in mind. High throughput usually comes from amortizing scheduling overhead across batches and from never letting any stage block a scheduler thread on slow I/O. Schedule I/O work where waiting is expected; keep pure computation on workers that stay busy.
How to wire senders and schedulers in practice
Start by naming your schedulers: one for compute, one for network or disk, and optionally one for a single-threaded affinity domain if you touch thread-local state. Build the lazy graph with pure transformations first. Only at the edge of the system do you submit the graph to a scheduler and connect a receiver that handles the three completion paths.
Prefer small, typed algorithms over giant lambdas. Split “map this batch,” “filter invalid items,” and “write the result” into separate sender adapters so each stage stays testable and replaceable. When two stages must run concurrently, fork the graph and join on both completions. When order matters, keep the chain sequential and move only independent work onto parallel branches.
Cancellation should be designed in early. Propagate stop tokens through long-running stages and check them between batches. A high-throughput pipeline that cannot stop cleanly under load will eventually waste more capacity than it gains from clever scheduling.
Zero-cost habits that keep the pipeline honest
Zero-cost concurrency is a discipline, not a free lunch. Avoid type-erased senders in the innermost loop; erasure is useful at API boundaries and costly when every item pays for it. Keep allocations off the hot path by reusing buffers and batching. Do not hide locks inside sender adapters that claim to be pure transforms.
Measure at the stage boundary you care about: items completed per unit time under sustained load, queue depth, and time spent blocked on I/O versus useful work. If a stage is always ready and the next stage is always full, you have a capacity problem, not a missing abstraction. Fix the bottleneck scheduler or the batching strategy before adding more composition.
Finally, treat the standardized model as the contract between libraries and applications. Share sender-shaped APIs so pipelines can be assembled across components without each one inventing its own executor vocabulary. That interoperability is what makes the C++26 design useful beyond a single codebase: the same lazy graph vocabulary scales from a short processing chain to a full multi-stage service path.