Ch6.10: fast_io views
Why this chapter exists
In the previous chapter, you learned that C-style array parameters and pointer+length pairs are error-prone:
- array size is lost when passed to a function
- pointer + length pairs are easy to mismatch
- no bounds checking
- ownership is unclear
- encourages unsafe patterns
Modern C++ solves this with view types: lightweight, non-owning objects that refer to existing memory without copying it.
The C++ standard library provides std::span and
std::string_view, but on Windows x86‑64,
std::span suffers from a calling‑convention limitation:
- the Windows x86‑64 ABI passes all 16‑byte objects via memory
std::spanis 16 bytes (pointer + size)- so it cannot be passed in registers
- this introduces unnecessary memory traffic
This ABI behavior was discovered by the author of this tutorial and
reported to Microsoft. Until the ABI is fixed, std::span is
less efficient on Windows x86‑64 than a simple (ptr, n) pair.
The wincall proposal
To address these long-standing ABI inefficiencies, the author proposed a new calling convention named wincall, designed for x86‑64 Windows under Intel APX. It is documented here:
Microsoft Developer Community: wincall proposal
and discussed here:
The motivation for wincall includes:
- the current Windows ABI passes too few parameters in registers
- 16‑byte objects (like
std::span) are forced onto the stack - Herbception (P0709) requires two registers + carry flag for
std::error - Intel APX expands the register file from 16 to 32 registers
- empty objects should not consume register slots
- Windows already uses caller-saved registers, making expansion feasible
wincall aims to:
- use more registers for parameter passing
- avoid penalizing small aggregates
- remain compatible with the Itanium C++ ABI
- coexist with stdcall and fastcall for DLL boundaries
- support efficient return of small user-defined types
This chapter does not depend on wincall, but understanding the ABI
limitations explains why std::span is inefficient on Windows
today.
Where fast_io fits in
The fast_io library does not “fix” the Windows
ABI. Instead:
- fast_io is not ABI-stable
- therefore it can choose layouts that avoid the 16‑byte penalty
- it provides safer semantics (bounds checking, explicit construction)
- it avoids the pitfalls of raw pointers and pointer+length pairs
In this chapter, you will learn how to use the following fast_io view types:
::fast_io::span<T>::fast_io::string_view::fast_io::cstring_view::fast_io::index_span<T, N>
These types are safer than raw pointers and avoid the Windows ABI
inefficiencies that affect std::span.
1. ::fast_io::span<T>
::fast_io::span<T> is a lightweight, non-owning view over a
contiguous sequence of T. It stores:
- a pointer
- a length
Unlike std::span, it is intentionally sized to avoid the
Windows x86‑64 16‑byte penalty. Because fast_io is not ABI-stable, it can
choose a representation that is efficiently passed in registers on all
major platforms.
Example: passing a span
void process(::fast_io::span<int const> s)
{
for(auto e : s)
::fast_io::io::println(e);
}
Example: creating a span from a vector
::fast_io::vector v{1,2,3,4};
process(::fast_io::span{v});
Example: creating a span from a raw array
int arr[4]{1,2,3,4};
process(::fast_io::span{arr});
2. ::fast_io::string_view
::fast_io::string_view is similar to std::string_view,
but designed to be ABI-friendly on all major platforms, including Windows
x86‑64. It:
- does not allocate
- does not own memory
- is efficiently passed to functions
- integrates with
fast_ioprinting
Example
void greet(::fast_io::string_view name)
{
::fast_io::println("Hello, ", name);
}
3. ::fast_io::cstring_view
::fast_io::cstring_view is a view over a null-terminated C
string. It stores only a pointer and computes the length when needed.
This is ideal for interfacing with C APIs or legacy code.
Example
void debug(::fast_io::cstring_view s)
{
::fast_io::io::println("debug: ", s);
}
4. ::fast_io::index_span<T, N>
::fast_io::index_span<T, N> is a fixed-size view over exactly
N elements. It:
- stores only a pointer
- performs bounds checking when indexing
- is useful for slicing without copying
Example
void print_slice(::fast_io::index_span<std::size_t, 5> idx)
{
for(std::size_t i{}, n{idx.size()}; i != n; ++i)
::fast_io::io::println(idx[i]);
}
5. Performance comparison
The key difference between std::span and
::fast_io::span is how they behave under different ABIs.
| Type | Size | Passed in registers (SysV) | Passed in registers (Win64) | Notes |
|---|---|---|---|---|
T* |
8 bytes | yes | yes | baseline pointer semantics |
(T*, std::size_t) |
16 bytes | yes | no | passed via memory on Win64 |
std::span<T> |
16 bytes | yes | no | same penalty as (ptr, n) |
::fast_io::span<T> |
8 bytes | yes | yes | avoids Win64 memory‑passing penalty |
::fast_io::span is intentionally sized to behave like a raw
pointer at the ABI level, ensuring efficient register passing on all major
platforms.
6. When to use each type
-
Use
::fast_io::span<T>for non-owning views of arrays or vectors. -
Use
::fast_io::string_viewfor UTF‑8 string slices. -
Use
::fast_io::cstring_viewwhen interfacing with C APIs. -
Use
::fast_io::index_span<T, N>for fixed-size slices and index ranges. - Avoid C-style array parameters and pointer+length pairs.
- Avoid std::span on Windows x86‑64 for performance-critical code.
Key takeaways
- fast_io view types provide safer alternatives to raw pointers.
- They avoid the Windows x86‑64 ABI penalty that affects
std::span. - They integrate naturally with
fast_iocontainers and printing. - They should replace C-style array parameters and pointer+length pairs.