await_suspend takes a type-erased coroutine_handle<> — the consumer type is already erased, so the awaitable's size is known at construction time. The sender equivalents add \~21–23 ns/op and one allocation per operation. The sender's connect(receiver) produces an op_state whose type depends on both the sender and the receiver. Since either may be erased, the operation state must be heap-allocated.Bridges are competitive. Both bridges add 11–17 ns for native streams with zero bridge allocations. The allocations visible in the bridged columns come from the target model's own machinery (type-erased connect, executor adapter posting), not from the bridges themselves.
std::execution provides compile-time sender composition, structured concurrency guarantees, and a customization point model that enables heterogeneous dispatch. These are real achievements for real domains — GPU dispatch, work-graph pipelines, heterogeneous execution. Coroutines serve a different domain. They cannot express compile-time work graphs or target heterogeneous dispatch. What they do is serial byte-oriented I/O — reads, writes, timers, DNS lookups, TLS handshakes — the work that networked applications spend most of their time on.# Trade-off Summary
|Feature|IoAwaitable|sender/receiver|
|:-|:-|:-|
|Native concrete performance|\~31 ns/op, 0 al/op|\~32–34 ns/op, 0 al/op|
|Type erasure cost|\+5 ns/op, 0 al/op|\+21–23 ns/op, 1 al/op|
|Type erasure mechanism|preallocated awaitable|heap-allocated op_state|
|Why erasure allocates|it does not|op_state depends on sender AND receiver types|
|Synchronous completion|\~1 ns/op via symmetric transfer|\~2.6 ns/op via trampoline|
|Looping|native for loop|requires
repeat_until \+ trampoline||Bridge to other model (native)|\~17 ns/op, 0 al/op|\~12 ns/op, 1 al/op|
|Bridge to other model (erased)|\~36 ns/op, 1 al/op|\~12 ns/op, 1 al/op|
https://redd.it/1shmwzz
@r_cpp