Type-Erased Wrappers

any_read_stream is a movable, type-erased wrapper for any type modeling ReadStream. It can be owning or non-owning: you decide upon construction. It models ReadStream itself.

It offers you a possibility to write an algorithm that works with any read stream and that is not a function template:

capy::task<> algo1(capy::ReadStream auto& stream); (1)

capy::task<> algo2(capy::any_read_stream& stream); (2)
1 Taking any read stream via a template argument.
2 Taking any read stream via a type-erased wrapper.

The type-erased wrapper comes at a slight run-time cost resulting from indirect calls and a possible allocator use. In return, you get:

  1. Not having to ship the implementation of your functions in header files.

  2. Much improved compile times.

  3. Being able to select the read stream implementation at runtime.

Note that this runtime cost is dwarfed by the cost of I/O itself.

The allocation strategy used by the wrapper is very efficient. There is only one when the wrapper is created. The same allocation is reused when an awaitable (also type-erased) is created by the call to read_some.

You decide whether the wrapper will exclusively own the stream, or if it will only be a reference, by selecting the appropriate constructor

MyReadStream s = makeMyStream();
capy::any_read_stream ws1(&s);             (1)
capy::any_read_stream ws2(makeMyStream()); (2)
1 Pointer constructor — You have to make sure the stream outlives the wrapper.
2 Rvalue reference constructor — The wrapper will manage the stream’s lifetime.

There are analogous wrappers for WriteStream and Stream concepts. This is shown in the table.

Concept Type-erased wrapper

ReadStream

any_read_stream

WriteStream

any_write_stream

Stream

any_stream