# Text and formatting In this chapter we replace the old C‑style formatting functions with the modern, type‑safe facilities that arrive in C++20 and C++23. The progression mirrors the lessons from the lifetime chapters: first we look at why `printf` is hazardous, then we introduce owning strings, non‑owning views, and finally the compile‑time checked formatting API. ## The C printf problem `printf` takes a format string that describes the types of the arguments that follow. The compiler cannot verify that the format string matches the argument list because the string is an ordinary `char const *`. If the programmer writes a mismatched conversion specifier or omits an argument, the program exhibits **undefined behavior** at run time. ```cpp // The format string expects an `int` and a `double`, but only one argument is supplied. printf("%d %f\n", 42) ``` The above code compiles, but at execution the function reads a non‑existent `double` from the stack. The result is nondeterministic and leads to memory corruption. ## std::string and std::string_view `std::string` owns the character storage. It allocates memory, frees it when the object is destroyed, and therefore always remains valid for the lifetime of the owning object. `std::string_view` does **not** own any characters. It merely holds a pointer and a length. The view is valid only while the underlying characters remain alive. Because a view never copies, it is ideal for function parameters: the caller can pass a `std::string`, a string literal, or a substring without allocating a new buffer. ```cpp void greet(std::string_view name) { std::println("Hello, {}!", name); } greet("Ada") // literal - no allocation std::string full = "Grace Hopper" greet(full) // temporary view of whole string greet(full.substr(0,5)) // view of a prefix ``` The function `greet` never copies the characters it simply reads them through the view. ## `std::format` (C++20) `std::format` replaces `printf` with a **type‑safe, variadic** formatting function. The format string contains `{}` placeholders. The compiler parses the literal at compile time, matches each placeholder with the corresponding argument, and rejects mismatches with a diagnostic. ```cpp {{#include ../../examples/ch10/format_mixed.cpp}} ``` Running the program prints a line that contains `Name: Alice`. The `EXPECT` test in the build system checks for that substring. ## `std::print` and `std::println` (C++23) `std::print` writes to `stdout` without appending a newline. `std::println` does the same but adds a newline automatically. The book uses `std::println` for line-oriented output. It uses `std::print` only when the output is a sequence of values on one line, written inside a loop, where a newline after each value is wrong. In that case the loop body calls `std::print("{} ", value)` and a single `std::println()` closes the line after the loop. ```cpp {{#include ../../examples/ch10/table_print.cpp}} ``` The example prints a small table. The test harness looks for the token `A` in the output. | Facility | Header | Type safety | Format checking | Returns | Use when | |---|---|---|---|---|---| | `std::cout` | `` | per insertion | none | stream reference | stream features, custom `operator<<` | | `printf` | `` | none | none | int count | C interop, legacy code | | `std::print` / `std::println` | `` | typed arguments | compile time | void | new code, checked formatting | ## Format specifiers The part of the format string after a colon controls alignment, width, fill character, precision, and numeric base. * **Width and alignment**: `{:<10}` left‑aligns inside a field of ten characters, `{:>10}` right‑aligns, `{:^10}` centers. * **Fill character**: `{:*^8}` pads with `*` while centering. * **Precision**: `{: .2f}` prints a floating‑point value with two digits after the decimal point. * **Base**: `{:#x}` prints an integer in hexadecimal with a `0x` prefix, `{:08b}` prints binary padded to eight digits. ```cpp std::println("{:>10}", "right") // " right" std::println("{:08b}", 5) // "00000101" std::println("{:#x}", 255) // "0xff" std::println("{:.2f}", 3.14159) // "3.14" ``` If a specifier is unknown, the compiler issues an error because the format string is a compile‑time constant. The specifier syntax mirrors Python’s `format`. The colon introduces a format‑spec, then optional fill, alignment, sign, alternate form, zero‑pad, width, precision, and type. The order matters. The compiler enforces it. Performance of `std::format` is comparable to hand‑written `printf` for simple cases. The library avoids temporary allocations for short strings by using a small‑buffer optimisation. Custom types can be formatted by specialising `std::formatter`. The specialization returns a `format_to` function that writes the representation into the provided output iterator. ```cpp struct Point { int x; int y; }; template<> struct std::formatter { constexpr auto parse(auto& ctx) { return ctx.begin(); } auto format(Point const& p, auto& ctx) const { return std::format_to(ctx.out(), "({},{})", p.x, p.y); } }; std::println("{}", Point{3,4}); // prints "(3,4)" ``` The same custom formatter works for both `std::println` and `std::format` because they share the formatter protocol. ## Advanced formatting features Beyond the basic width and precision specifiers, `std::format` supports a rich set of options that let developers tailor the textual representation of values. * **Sign handling**: `{: +}` forces a leading plus sign for positive numbers, while `{: -}` (the default) prints a minus sign only for negatives. * **Alternate form**: The `#` flag adds a prefix for certain types: `0x` for hexadecimal, `0` for octal, and a trailing decimal point for floating‑point values. * **Zero padding**: `{:08}` pads the field with zeros instead of spaces. It is equivalent to `{:0>8}` but more concise. * **Grouping**: `{:L}` formats numbers according to the locale's thousands separator. This works together with a `std::locale` overload. * **Date and time**: When the `` library provides a `std::chrono::year_month_day` or `std::chrono::hh_mm_ss` object, the formatter can emit ISO‑8601 strings using the `{:T}` or `{:D}` specifiers. ```cpp std::println("{:+08}", 42) // "+0000042" std::println("{:#x}", 255) // "0xff" std::println("{:L}", 1234567) // "1,234,567" in en_US locale using namespace std::chrono auto now = floor(system_clock::now()) std::println("{:T}", now) // "2026-08-21T14:35:00" ``` These specifiers are composable a format string can combine alignment, fill, width, sign, and type in a single placeholder. The compiler checks that the combination is valid for the argument type, rejecting illegal mixes such as a sign flag on a string. Custom types can also honour these flags by inspecting the `format_context` and the parsed format‑spec. A formatter can honour `fill` and `align` by delegating to `std::format_to` with a constructed format string, or it can implement the formatting logic directly for maximum performance. ```cpp struct Money { int cents; }; template<> struct std::formatter { char presentation = 'f'; // f = dollars.cents, e = euros constexpr auto parse(auto& ctx) { auto it = ctx.begin(); if (it != ctx.end() && (*it == 'e' || *it == 'f')) presentation = *it++; return it; } auto format(Myney const& m, auto& ctx) const { if (presentation == 'e') return std::format_to(ctx.out(), "€{:.2f}", m.cents / 100.0); else return std::format_to(ctx.out(), "${:.2f}", m.cents / 100.0); } }; std::println("{}", Money{1234}) // prints "$12.34" ``` The example demonstrates how a formatter can respect a presentation specifier while still supporting the generic alignment and width options supplied by the surrounding format string. Placeholders can refer to arguments by position. ```cpp std::println("{0} + {0} = {1}", 2, 4) // prints "2 + 2 = 4" ``` Named arguments are not part of the core standard, but a user can achieve them by providing a custom `std::formatter` specialization or by using `std::make_format_args` together with a format string that references names via a library such as {fmt}. The book mentions the technique briefly because it is useful in larger projects. ```cpp // Using a custom formatter (illustrative - not compiled here) struct Point { int x int y } template<> struct std::formatter : std::formatter { auto format(Point const& p, auto& ctx) const { return std::formatter::format( std::format("({},{})", p.x, p.y), ctx) } } ``` ## Compile‑time checking is the point `std::format` only accepts a **compile‑time constant** format string for full checking. If a program needs a run‑time format, the library provides `std::runtime_format`, which disables compile‑time verification. This design forces the programmer to choose safety whenever possible. Contrast the two approaches: * `printf("%d %f\n", 42)`: compiles, can crash at run time. * `std::format("%d %f\n", 42)`: fails to compile because `%` is not a valid placeholder. * `std::format(std::runtime_format(fmt), 42)`: compiles, but the format string is unchecked. The book’s theme is to let the compiler catch what it can, and `std::format` embodies that principle. ## Performance and constexpr formatting `std::format` is designed to be fast. The implementation parses the format string at compile time when the literal is a constant expression, eliminating runtime parsing overhead. This makes it comparable to hand‑written `printf` for simple cases while providing safety. When the format string cannot be known at compile time, the library falls back to a runtime parser. The cost is modest: a single pass over the format string plus the usual formatting work. For tight loops where every nanosecond matters, developers can still write a custom `printf`‑style loop, but the safety trade‑off must be justified. C++23 extends `std::format` with `constexpr` support. A `constexpr` function can call `std::format` to produce a compile‑time constant string that can be used as a non‑type template parameter or in a `static_assert`. ```cpp constexpr std::string_view make_label(int id) { return std::format("Item-{:03}", id) } static_assert(make_label(7) == "Item-007") ``` The example illustrates how formatting can participate in compile‑time computation, enabling expressive metaprogramming without sacrificing safety. Locale‑aware formatting is optional. By default `std::format` uses the "C" locale, which formats numbers with a period as the decimal separator. Passing a `std::locale` object to the overload selects the suitable digit grouping and decimal marks for the target locale. ```cpp std::locale german("de_DE") std::println(std::format(german, "{:L}", 1234567.89)) // prints "1.234.567,89" ``` Custom formatters, shown earlier, work uniformly with locale‑aware overloads because the formatter receives the locale via its `format` method. ## Error handling in formatting When a format string is ill‑formed, `std::format` throws a `std::format_error`. This exception type is a subclass of `std::runtime_error` and carries a message that identifies the problem, for example, "argument index out of range" or "invalid format specifier". ```cpp try { std::println(std::format("{0} {2}", 1, 2)) // index 2 does not exist } catch (const std::format_error& e) { std::cerr << "Formatting failed: " << e.what() << '\n' } ``` The library also provides the non‑throwing overload `std::format_to_n` which writes into a pre‑allocated buffer and returns the number of characters written. This is useful in low‑latency or embedded contexts where exceptions are disabled. ```cpp char buf[32] auto result = std::format_to_n(buf, sizeof(buf), "{:04x}", 0x1A3) std::println("{} characters written", result.out - buf) ``` `result.out` points just past the last character written, allowing the caller to construct a `std::string_view` without an extra copy. The library also defines `std::vformat` and `std::vprint` which take a `std::format_args` object generated by `std::make_format_args`. These functions enable runtime‑determined argument lists while still performing compile‑time checks on the format string itself. ```cpp auto args = std::make_format_args(42, 3.14) std::println(std::vformat("int={}, double={}", args)) ``` If the format string itself is not a constant expression, the parser cannot validate the placeholders against the arguments at compile time. In that case, the same run‑time checks apply, and mismatches still result in a `std::format_error`. The distinction between compile‑time guarantees and run‑time safety mirrors the earlier discussion of `printf`. By default the library favours compile‑time safety developers can opt into run‑time flexibility when the application requires it. --- ## Try this Write a program that prints a three‑row table. Each column must have a fixed width. The second column contains floating‑point numbers printed with two decimal places and right‑aligned. ```cpp // Insert your own code here - use std::println and format specifiers. ``` When you run the program, the output must look like a tidy table with aligned columns. --- > **NOTE**: All examples in this chapter are compiled and tested with the Clang 22 toolchain using `-std=c++26`. The `book_example` macro registers each source file as a test target, and the `EXPECT` strings verify that the output contains the expected fragments.