System I/O Integration
This section explains how buffer sequences interface with operating system I/O operations.
Prerequisites
-
Completed Buffer Sequences
-
Understanding of buffer sequence concepts
The Virtual Boundary
User-facing APIs use concepts for composition flexibility. But at type-erasure boundaries—where virtual functions are needed—concrete types are required.
Capy’s design:
-
User-facing API — Accepts
ConstBufferSequenceorMutableBufferSequenceconcepts -
Internal boundary — Converts to concrete arrays for virtual dispatch
-
OS interface — Translates to platform-specific structures
The library handles all conversions automatically.
Platform Buffer Structures
Translation Process
When you call an I/O function with a buffer sequence:
template<ConstBufferSequence Buffers>
io_task<std::size_t> write_some(Buffers buffers);
Internally, Capy:
-
Counts the number of buffers in the sequence
-
Allocates space for platform buffer structures (on stack for small sequences)
-
Copies buffer descriptors (pointer/size pairs) to platform structures
-
Calls the OS function with the platform array
-
Returns the result
Stack-Based Conversion
Conversion always happens on the stack—the implementation never allocates. A fixed-size, on-frame window of buffer descriptors (16 entries) is filled from the sequence and passed to the OS call. If the sequence has more buffers than fit in the window, the window is refilled and the OS call is repeated for the remaining buffers:
// Pseudocode of internal implementation
template<ConstBufferSequence Buffers>
auto platform_write(Buffers const& buffers)
{
iovec iovecs[16]; // fixed on-frame window, never heap-allocated
auto it = begin(buffers);
auto last = end(buffers);
while (it != last)
{
std::size_t count = fill_iovecs(iovecs, it, last, 16); // up to 16
auto result = writev(fd, iovecs, count);
// ... advance the window past the buffers just written
}
}
The window size (16) is fixed and implementation-defined. Sequences with more buffers than the window are handled by refilling it across successive OS calls; there is no heap fallback.
Scatter/Gather Benefits
Using vectored I/O provides:
Fewer System Calls
Without scatter/gather:
write(fd, header, header_len); // syscall 1
write(fd, body, body_len); // syscall 2
With scatter/gather:
iovec iov[2] = {{header, header_len}, {body, body_len}};
writev(fd, iov, 2); // single syscall
Registered Buffers
Advanced platforms offer registered buffer optimizations:
Writing Efficient Code
Minimize Buffer Count
Fewer buffers means less translation overhead:
// Prefer: single buffer when possible
auto buf = assemble_message(); // Build in one buffer
co_await write(stream, buf);
// Avoid: many tiny buffers
std::array<const_buffer, 100> tiny_bufs;
co_await write(stream, tiny_bufs); // 100-element translation
Reuse Buffer Structures
For repeated I/O with the same structure, consider caching the platform buffer array:
// Build once, use many times
struct message_buffers
{
std::array<iovec, 3> iovecs;
void set_header(void const* p, std::size_t n);
void set_body(void const* p, std::size_t n);
void set_footer(void const* p, std::size_t n);
};
Reference
The buffer sequence concepts and translation utilities are in:
// Fragments deliberately leave results and bindings unused; the pages
// explain the values in prose instead.
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-value"
#pragma GCC diagnostic ignored "-Wunused-result"
#pragma GCC diagnostic ignored "-Wunused-function"
// gcc 15 with sanitizers misattributes coroutine frame delete paths
#pragma GCC diagnostic ignored "-Wmismatched-new-delete"
#endif
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wunused-lambda-capture"
#pragma clang diagnostic ignored "-Wunused-private-field"
#endif
#if defined(_MSC_VER)
#pragma warning(disable: 4834) // discarding [[nodiscard]] return value
#pragma warning(disable: 4189) // local variable initialized but not referenced
#pragma warning(disable: 4100) // unreferenced formal parameter
#pragma warning(disable: 4101) // unreferenced local variable
#pragma warning(disable: 4456) // declaration hides previous local declaration
#pragma warning(disable: 4457) // declaration hides function parameter
#pragma warning(disable: 4458) // declaration hides class member
#pragma warning(disable: 4459) // declaration hides global declaration
#endif
#include <boost/capy/buffers.hpp>
OS-specific I/O is handled by Corosio, which builds on Capy’s buffer model.
You have now learned how buffer sequences integrate with operating system I/O. Continue to Buffer Algorithms to learn about measuring and copying buffers.