Improvements to std::format in C++26

Lobsters Hottest News

Summary

The C++26 standard introduces several improvements to the std::format library, including direct pointer formatting, path formatting, constexpr support, and a new empty line overload for std::println.

<p><a href="https://lobste.rs/s/xtuz4x/improvements_std_format_c_26">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 06/22/26, 01:30 AM

# Improvements to std::format in C++26 Source: [https://mariusbancila.ro/blog/2026/06/19/improvements-to-stdformat-in-c26/](https://mariusbancila.ro/blog/2026/06/19/improvements-to-stdformat-in-c26/) The C\+\+26 standard features a series of improvements to the format library\. In this article, we will look at the most important of them\. ## Printing an empty line Prior to C\+\+26, printing an empty line had to be done like this: ``` std::print("\n"); ``` In C\+\+26,`std::println`has an overload without any parameters that prints a new line to the console\. ``` std::print(); ``` ## Formatting pointers Formatting pointer types was not available directly, it required a hack: reinterpreting the pointer type as an integer type in order to print it\. ``` int i = 0; const void* p = &i; std::println("{:#018x}", reinterpret_cast<uintptr_t>(p)); ``` In C\+\+26, the formatting library supports formatting of pointer types directly: - implicitly, no specifier is required - explicitly, with wither the`p`\(for lowercase\) or`P`\(for uppercase\) specifiers Null pointers are formatted as`0x0`\(unless padding is specified\)\. Here are several examples of formatting pointers: ``` int i = 0; const void* p = &i; std::println("{}", p); // lowercase, the default => 0x7fffb2715a54 std::println("{:p}", p); // explicit, same as none => 0x7fffb2715a54 std::println("{:P}", p); // uppercase => 0X7FFFB2715A54 std::println("{:018}", p); // zero-padded to width 18 => 0x00007fffb2715a54 std::println("{:>20}", p); // right-aligned in a 20-wide field => 0x7fffb2715a54 std::println("{}", nullptr); // => 0x0 std::println("{:016}", nullptr); // => 0x00000000000000 ``` ## Formatting paths Another feature that required a workaround was printing paths from the`std::filesystem`namespace\. You could use`path::string\(\)`to get the string representation of a path\. ``` namespace fs = std::filesystem; fs::path p = "/usr/local/bin/clang++"; std::println("{}", p.string()); ``` However, this prints the path unquoted\. On the other hand, using the`<<`operator would print the path in quotes: ``` std::cout << p << '\n'; ``` C\+\+26 adds a`std::formatter`for`std::filesystem::path`, which makes it easier to format paths\. - by default, paths are formatted unquoted - the`?`option defines a debug form which gives an escaped representation \(in quotes\) - the`g`option forces generic \(forward\-slash\) separators \(which mainly shows up on Windows\) ``` fs::path p = "/usr/local/bin/clang++"; std::println("{}", p); // /usr/local/bin/clang++ std::println("{:?}", p); // "/usr/local/bin/clang++" ``` ``` fs::path p = R"(C:\Users\marius\file.txt)"; std::println("{}", w); // C:\Users\marius\file.txt (native separators) std::println("{:g}", w); // C:/Users/marius/file.txt (generic) std::println("{:g?}", w); // "C:/Users/marius/file.txt" (generic + escaped) ``` A related issue solved along was Windows string representation of paths\.`std::filesystem::path`stores its text in`wchar\_t`encoded as UTF\-16 \(Windows native\)\. But`p\.string\(\)`narrows it down to the active code page, rather than UTF\-8 which is what the formatting library expects\. The result was a non\-ASCII path could get transcoded to gibberish\. The C\+\+26`std::formatter<std::filesystem::path\>`converts Windows native UTC\-16 to UTF\-8 using Unicode transcoding and avoiding code pages, therefore solving the problem\. Ill\-formed UTF\-16 is replaced with`U\+FFFD`by default, or escaped under`\{:?\}`\. ## constexpr std::format In C\+\+26, the formatting functions`std::format`,`std::vformat`,`std::format\_to`,`std::format\_to\_n`,`std::formatted\_size`, and their wide variants, plus the underlying pieces \(the format context,`std::basic\_format\_arg`,`std::basic\_format\_string`, and the format member of the standard formatters\) are`constexpr`\. This makes it possible to use`static\_assert`for instance with`std::format`such as in the following examples: ``` static_assert(std::format("{} {}", 1, 2) == "1 2"); static_assert(sizeof(void*) == 8, std::format("expected 64-bit, pointer is {} bytes", sizeof(void*))); ``` This works because it relies on`to\_chars\(\)`overloads which have been made`constexpr`, but only for integral types\. So it can be used with strings, integer types,`bool`,`char`, and`pointers`\. But there are several limitations to this feature\. The following are not supported: - floating\-point types - chrono types - locale\-aware formatting \(using the`L`specifier – as in`\{:L\}`, makes the call non\-constant\) For now, compile\-time`std::format`covers integers, strings, and diagnostics well, with floating\-point support waiting on a separate paper \(P3652\) to make the floating\-point`<charconv\>`functions`constexpr`\. ## std::runtime\_format becomes std::dynamic\_format The format string of`std::format`or`std::print`must be a constant expression\. For instance, you can write this: ``` std::println("{} = {}", "x", 13); ``` But you cannot write the following: ``` std::string strf = "{} = {}"; std::println(strf, "x", 13); ``` This is ill\-formed because`strf`is only known at runtime, and therefore is not a constant expression\. The workaround is to use`std::vformat`: ``` const char* key = "y"; int val = 13; std::string strf = "{} = {}"; std::string s = std::vformat(strf, std::make_format_args(key, val)); std::println("{}", s); ``` There is a workaround for the workaround \(it’s basically syntactic sugar for`std::vformat`\), the formally known as`std::runtime\_format`function, that returns an object that stores a dynamic format string directly usable in user\-oriented formatting functions and can be implicitly converted to`std::basic\_format\_string`\. ``` std::string strf = "{} = {}"; std::println(std::runtime_format(strf), "x", 13); ``` In the final version of C\+\+26, this has been simply renamed to`std::dynamic\_format`\(although at this time the compilers that do support it still use the previous name\)\. Therefore, the snippet above now becomes: ``` std::string strf = "{} = {}"; std::println(std::dynamic_format(strf), "x", 13); ``` `std::dynamic\_format`is constexpr, which means it can be used in constant\-evaluation contexts, such as in the following example: ``` constexpr auto make_string = [](std::string_view f) { return std::format(std::dynamic_format(f), 1, 2); }; static_assert(make_string("{}+{}") == "1+2"); ``` ## See more You can learn more about these changes from the following articles: - [C\+\+26: std::format improvement \(Part 1\)](https://www.sandordargo.com/blog/2025/07/09/cpp26-format-part-1) - [C\+\+26: std::format improvements \(Part 2\)](https://www.sandordargo.com/blog/2025/07/16/cpp26-format-part-2) - `constexpr std::format`

Similar Articles

C++26: More function wrappers

Lobsters Hottest

C++26 introduces two new function wrappers: std::copyable_function, which provides a copyable, const-correct alternative to std::function, and std::function_ref, a non-owning callable reference with reference semantics.

C++26: Standard library hardening

Lobsters Hottest

C++26 is introducing standardized library hardening to catch common undefined behavior (like out-of-bounds access) at runtime, based on Google's production experience showing a mere 0.30% performance overhead and a 30% reduction in segmentation faults.

The C++ Standard Library Has Been Walking Itself Back for Fifteen Years, and the Receipts Are Public

Lobsters Hottest

A detailed catalogue of C++ standard library features that have been formally deprecated, informally discouraged, or are effectively broken but cannot be fixed due to ABI constraints, spanning from C++11 to C++26. The article argues a consistent pattern of the C++ committee shipping replacements for its own features, including a benchmark showing 58x P99 latency difference between Rust and C++ standard library containers.