Ch11.9: Relationship with String
One System, Everywhere
The concat_fast_io and to<T> APIs introduced
in Ch9.12 are not a separate
string-formatting pipeline — they are the same I/O system that
powers print, println, and scan. Every
string operation in fast_io funnels through the library's core
I/O layer.
This means the customisation points you learned for console and file output
— print_define and print_reserve_define
— automatically make your types work with string APIs too. There is
nothing extra to implement.
concat_fast_io Uses print_reserve_define
When you call concat_fast_io, the library formats each argument
into a string by invoking the same print_reserve_define (or
print_define) that println uses. If your type is
printable to the console, it is printable into a string — for free.
For example, suppose we defined print_reserve_define for a
Point struct back in
Ch11.2:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
struct Point {
double x;
double y;
};
inline constexpr void print_reserve_define(::fast_io::io_reserve_type_t<char, Point>,
char* iter,
Point const& p) {
// Format: "(x, y)"
*iter++ = '(';
iter = ::fast_io::detail::print_reserve_integral(iter, p.x);
*iter++ = ',';
*iter++ = ' ';
iter = ::fast_io::detail::print_reserve_integral(iter, p.y);
*iter++ = ')';
}
int main() {
using namespace ::fast_io::iomnp;
Point p{3.0, 4.0};
// Works on the console
println("Console: ", p);
// Also works in concat_fast_io — same customisation point
::fast_io::string s = ::fast_io::concat_fast_io("Point: ", p);
// s == "Point: (3, 4)"
println(s);
}
No separate to_string function, no
std::format specialisation, no operator<<
overload. One definition, every output target.
to<T> Uses the Same I/O Pipeline
to<T> works in two stages, both of which go through the
standard I/O pipeline:
-
Format — the arguments are formatted into a
temporary string using
concat_fast_io(and thereforeprint_reserve_define). -
Parse — the resulting string is scanned into the
target type
Tusingscan_define, the same customisation point thatscanuses.
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
// Format 42 as a string ("42"), then parse it back as a double
double d = ::fast_io::to<double>(42); // d == 42.0
// Concatenate multiple arguments, then parse the result
::std::size_t n = ::fast_io::to<::std::size_t>(1zu, 23zu);
// "1" + "23" -> "123" -> 123
println("d = ", d, ", n = ", n);
}
Because both stages reuse the standard pipeline, any custom type that
implements print_reserve_define and
scan_define can round-trip through to<T>
without any additional glue code.
The Unified Design
The takeaway is that fast_io has one set of
customisation points for every output target:
| Customisation Point | Used By |
|---|---|
print_define / print_reserve_define |
println, print, concat_fast_io,
concatln_fast_io, to<T>
|
scan_define / scan_context_define |
scan, to<T> (parse stage) |
Console, file, buffered I/O, string, socket — they all share the same formatting and parsing layer. Define your type once and it works everywhere.
Writing to Existing Strings: {chtype}ostring_ref_fast_io
While concat_fast_io creates a new string, sometimes you want to
format output directly into an existing string buffer. The
::fast_io::{chtype}ostring_ref_fast_io family of types lets you do exactly that.
These are non-owning reference types that wrap an existing string and expose it as an output
device for print, println, and other output operations.
The {chtype} placeholder is replaced with the character type:
::fast_io::ostring_ref_fast_io— forcharstrings::fast_io::u8ostring_ref_fast_io— forchar8_t/ UTF-8 strings::fast_io::u16ostring_ref_fast_io— forchar16_t/ UTF-16 strings::fast_io::u32ostring_ref_fast_io— forchar32_t/ UTF-32 strings::fast_io::wostring_ref_fast_io— forwchar_tstrings
You construct a reference by passing a pointer to an existing string. The string is not cleared before writing — output is appended at the current position. When you are done, the string contains everything that was written.
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main()
{
using namespace ::fast_io::iomnp;
::fast_io::u8string result;
// Wrap the existing string as an output device
::fast_io::u8ostring_ref_fast_io ref(result);
// Write to the string using print/println
print(ref, "Hello, ");
print(ref, "World!");
println(ref); // adds newline
// The string now contains "Hello, World!\n"
println("Result: ", result);
// You can also write structured data
::fast_io::u8string buffer;
::fast_io::u8ostring_ref_fast_io buf_ref(buffer);
::std::size_t const x{42zu};
::std::size_t const y{100zu};
println(buf_ref, "Point: (", x, ", ", y, ")");
// buffer now contains "Point: (42, 100)\n"
println(buffer);
}
This is particularly useful when you need to build a string incrementally, or when you want to reuse a string buffer to avoid repeated allocations. The reference type itself is lightweight — it just holds a pointer to the string.
Note that these types are different from ::fast_io::u8string and similar owning
string types. The _ref_ in the name indicates that these are non-owning
references — they do not manage the lifetime of the underlying string. You must
ensure the string outlives the reference.
Key Takeaways
-
concat_fast_iois not a separate formatter — it callsprint_reserve_defineinternally, the same entry pointprintlnuses. -
to<T>formats to a string viaconcat_fast_io, then parses withscan_define. Both stages use the standard I/O pipeline. -
Any custom type that implements
print_reserve_defineautomatically works withconcat_fast_ioand the format stage ofto<T>. -
Implement both
print_reserve_defineandscan_defineand your type can round-trip throughto<T>for free. - This is the unified design: one set of customisation points for console, file, string, and socket output.
-
Use
::fast_io::{chtype}ostring_ref_fast_ioto write formatted output directly into an existing string buffer. These non-owning reference types let you build strings incrementally or reuse buffers to avoid allocations.