C++26 中 std::format 的改进

Lobsters Hottest 新闻

摘要

C++26 标准对 std::format 库进行了多项改进,包括直接指针格式化、路径格式化、constexpr 支持,以及为 std::println 新增的空行重载。

<p><a href="https://lobste.rs/s/xtuz4x/improvements_std_format_c_26">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/22 01:30

# C++26中std::format的改进 来源:https://mariusbancila.ro/blog/2026/06/19/improvements-to-stdformat-in-c26/ C++26标准对format库进行了一系列改进。本文将介绍其中最重要的改进。 ## 打印空行 在C++26之前,打印空行必须这样写: ```cpp std::print("\n"); ``` 在C++26中,`std::println`有一个不带任何参数的重载,它向控制台打印一个换行符: ```cpp std::println(); // 注意:原文为 std::print(); 实际应为 std::println(); 但按原文翻译 ``` (注:原文此处的示例代码写的是 `std::print();`,但实际C++26中可能是 `std::println();`,此处保留原文示例。) ## 格式化指针 之前无法直接格式化指针类型,需要一种变通方法:将指针类型重新解释为整数类型来打印。 ```cpp int i = 0; const void* p = &i; std::println("{:#018x}", reinterpret_cast<std::uintptr_t>(p)); ``` 在C++26中,格式化库直接支持格式化指针类型: - 隐式:无需指定格式说明符 - 显式:使用 `p`(小写)或 `P`(大写)说明符 空指针会格式化为 `0x0`(除非指定了填充)。以下是指针格式化的几个示例: ```cpp int i = 0; const void* p = &i; std::println("{}", p); // 默认小写 => 0x7fffb2715a54 std::println("{:p}", p); // 显式,与不写相同 => 0x7fffb2715a54 std::println("{:P}", p); // 大写 => 0X7FFFB2715A54 std::println("{:018}", p); // 零填充至宽度18 => 0x00007fffb2715a54 std::println("{:>20}", p); // 右对齐,字段宽度20 => 0x7fffb2715a54 std::println("{}", nullptr); // => 0x0 std::println("{:016}", nullptr); // => 0x00000000000000 ``` ## 格式化路径 另一个需要变通方案的功能是从 `std::filesystem` 命名空间打印路径。可以使用 `path::string()` 来获取路径的字符串表示: ```cpp namespace fs = std::filesystem; fs::path p = "/usr/local/bin/clang++"; std::println("{}", p.string()); ``` 但这会打印不带引号的路径。另一方面,使用 `<<` 运算符会打印带引号的路径: ```cpp std::cout << p << '\n'; ``` C++26为 `std::filesystem::path` 添加了 `std::formatter`,使得格式化路径更加方便: - 默认情况下,路径格式化为不带引号 - `?` 选项定义调试形式,给出转义表示(带引号) - `g` 选项强制使用通用(正斜杠)分隔符(主要在Windows上体现) ```cpp fs::path p = "/usr/local/bin/clang++"; std::println("{}", p); // /usr/local/bin/clang++ std::println("{:?}", p); // "/usr/local/bin/clang++" ``` ```cpp fs::path p = R"(C:\Users\marius\file.txt)"; std::println("{}", p); // C:\Users\marius\file.txt (原生分隔符) std::println("{:g}", p); // C:/Users/marius/file.txt (通用分隔符) std::println("{:g?}", p); // "C:/Users/marius/file.txt" (通用+转义) ``` 与之相关的一个已解决问题是Windows上路径的字符串表示。`std::filesystem::path` 将其文本存储在以UTF-16(Windows原生)编码的 `wchar_t` 中。但 `p.string()` 将其缩小到活动代码页,而不是格式化库期望的UTF-8。这会导致非ASCII路径被转码为乱码。C++26的 `std::formatter` 使用Unicode转码将Windows原生的UTF-16转换为UTF-8,避免了代码页问题。默认情况下,格式错误的UTF-16会被替换为 `U+FFFD`,或在 `{:?}` 下被转义。 ## constexpr std::format 在C++26中,格式化函数 `std::format`、`std::vformat`、`std::format_to`、`std::format_to_n`、`std::formatted_size` 及其宽字符变体,以及底层部件(格式化上下文、`std::basic_format_arg`、`std::basic_format_string` 和标准格式化器的 `format` 成员)都是 `constexpr` 的。这使得可以在 `static_assert` 中使用 `std::format`,例如: ```cpp static_assert(std::format("{} {}", 1, 2) == "1 2"); static_assert(sizeof(void*) == 8, std::format("expected 64-bit, pointer is {} bytes", sizeof(void*))); ``` 这之所以可行,是因为它依赖于已变为 `constexpr` 的 `to_chars()` 重载,但仅针对整数类型。因此它可以用于字符串、整数类型、`bool`、`char` 和指针。但此功能有一些限制。以下情况不支持: - 浮点类型 - chrono 类型 - 本地环境感知格式化(使用 `L` 说明符,例如 `{:L}`,会使调用非常量) 目前,编译期 `std::format` 很好地覆盖了整数、字符串和诊断信息,浮点支持则等待另一篇论文(P3652)将浮点 `to_chars` 函数变为 `constexpr`。 ## std::runtime_format 变为 std::dynamic_format `std::format` 或 `std::print` 的格式字符串必须是常量表达式。例如,可以这样写: ```cpp std::println("{} = {}", "x", 13); ``` 但不能这样写: ```cpp std::string strf = "{} = {}"; std::println(strf, "x", 13); ``` 这是病态的,因为 `strf` 只在运行时已知,不是常量表达式。变通方法是使用 `std::vformat`: ```cpp 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); ``` 还有一种对变通方案的变通(基本上是 `std::vformat` 的语法糖),即以前称为 `std::runtime_format` 的函数,它返回一个对象,该对象存储可在面向用户的格式化函数中直接使用的动态格式字符串,并能隐式转换为 `std::basic_format_string`: ```cpp std::string strf = "{} = {}"; std::println(std::runtime_format(strf), "x", 13); ``` 在C++26的最终版本中,这个函数被简单地重命名为 `std::dynamic_format`(尽管目前支持它的编译器仍使用旧名称)。因此,上面的代码段现在变成了: ```cpp std::string strf = "{} = {}"; std::println(std::dynamic_format(strf), "x", 13); ``` `std::dynamic_format` 是 `constexpr` 的,这意味着它可以在常量求值上下文中使用,例如: ```cpp constexpr auto make_string = [](std::string_view f) { return std::format(std::dynamic_format(f), 1, 2); }; static_assert(make_string("{}+{}") == "1+2"); ``` ## 了解更多 你可以从以下文章了解更多关于这些更改的信息: - 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`

相似文章

C++26:新的调试头文件

Hacker News Top

C++26 引入了新的 <debugging> 头文件,提供 std::breakpoint()、std::is_debugger_present() 和 std::breakpoint_if_debugging(),通过提案 P2546R5 和 P2810R4 实现标准化,以取代平台特定的调试宏。

fmtlib/fmt

GitHub Trending (daily)

fmtlib/fmt 是一个开源的 C++ 格式化库,提供快速且安全的替代方案,用于 C stdio 和 C++ iostreams,并实现了 C++20 的 std::format 和 C++23 的 std::print。

C++26:减少未定义行为

Lobsters Hottest

C++26 引入了减少未定义行为的更改,特别是使删除指向不完整类型的指针成为格式错误,从而提高了程序安全性。

C++26:更多函数包装器

Lobsters Hottest

C++26 引入了两个新的函数包装器:std::copyable_function(提供了可复制且 const 正确的 std::function 替代品)和 std::function_ref(一个非拥有、可调用的引用,具有引用语义)。