std::format¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++20 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-26 |
编译器支持:GCC 13+ · Clang 17+ · MSVC 19.29+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 特性可用时用 std::format 生成 SQL 浮点参数,旧工具链回退到 stringstream | ev-drogon-format-sql-double-001 |
| 为自定义类型特化 std::formatter 并复用 std::format_to | ev-spdlog-std-format-custom-type-002 |
| 切换 std::format 后端时统一格式化字符串类型 | ev-spdlog-std-format-binding-003 |
本站证据页:ev-drogon-format-sql-double-001 · ev-spdlog-std-format-binding-003 · ev-spdlog-std-format-custom-type-002
解决什么问题¶
printf 的格式串和实参类型分离,类型不匹配是未定义行为;std::cout 没有格式串,控制宽度、精度和进制要反复设置流状态。两者都缺少“格式串在编译期检查”的能力。
std::format 把 Python/fmt 风格的格式串带进标准库:普通字面量格式串在编译期检查,类型不匹配直接报错;结果返回 std::string,不依赖流状态;还提供 format_to、format_to_n 等写入迭代器的变体,避免临时字符串。
基础用法¶
#include <format>
#include <iostream>
#include <string>
int main() {
std::string text = std::format("{:.2f} {} {}", 3.14159, "atlas", 42);
std::cout << text << '\n';
}
{:.2f} 固定两位小数,{} 按类型选择默认格式。格式串是字面量时,编译器会检查占位符数量与实参类型;要复用运行期格式串,必须改用 std::vformat,并接受失去编译期检查的代价。
进阶用法¶
自定义类型需要特化 std::formatter:parse 处理格式说明符,format 把值写入输出迭代器。实现内部可以继续调用 std::format_to,复用已有的格式化逻辑。
struct Point {
int x;
int y;
};
namespace std {
template <> struct formatter<Point> {
constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); }
auto format(const Point &p, format_context &ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
} // namespace std
这个 formatter 只接受默认格式说明符;format 返回输出迭代器,可以继续链式写入。宽度、精度等说明仍按内置类型的语法书写,例如 {:>8.2f};用 std::format_to 写入 back_inserter 时不会产生中间字符串。
完整示例见 examples/cpp20/format.cpp。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| Drogon | orm_lib/src/SqlBinder.cc#L286-L292 |
用 std::format 生成固定精度的 SQL 浮点参数 |
生产路径需要稳定的浮点文本;特性宏让旧工具链回退到 stringstream |
| spdlog | example/example.cpp#L283-L289 |
为自定义类型特化 std::formatter 并调用 format_to |
项目同时支持 fmt 与 std::format,切换后端不改变调用方写法 |
| spdlog | include/spdlog/common.h#L145-L156 |
切换 std::format 后端时统一格式化字符串类型 |
公共接口同时兼容 fmt 与标准库,别名让后端差异止于头文件 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。前两条分别覆盖生产路径与分发示例,第三条展示后端切换时如何统一格式化字符串类型。
陷阱与边界¶
编译期检查只对常量格式串生效。 std::format(runtime_string, value) 不能通过 std::format_string 的检查;运行期格式串应使用 std::vformat,并自行承担格式错误的风险。
std::format 返回新字符串。 在热路径或大文本场景,优先用 std::format_to 写入已有缓冲区;std::format_to_n 可以限制输出长度,但不会自动截断到合法 UTF-8 边界。
自定义类型需要 formatter。 标准库不会通过 ADL 寻找 format_as;要为类型提供 std::formatter<T> 特化,实现 parse 与 format。这与 fmt 的扩展点相似,但不能直接复用 fmt 的 formatter。
默认不跟随全局 locale。 不要把 std::format 的输出与 std::cout 当前 locale 下的格式混为一谈;需要本地化数字或日期时,使用接受 locale 的接口或专门的本地化库。
格式串语法不是 printf。 %d、%s 不适用;花括号要转义为 {{、}}。命名参数也不是 C++20 std::format 的一部分,不要照搬 fmt 的扩展语法。
编译器支持差异要用特性宏判断。 使用 __cpp_lib_format 检测 <format>;GCC 13、Clang 17 与 MSVC 19.29 才提供完整实现,旧标准库可能缺少部分格式化器。
浮点输出依赖实现细节。 标准规定了格式语法,但最短表示、舍入和特殊值文本在库实现之间可能有细微差异;跨平台测试要比较解析后的值,而不是逐字符比较。
库联动¶
- Drogon:SQL 绑定器在特性宏下用
std::format生成固定精度的浮点参数,旧工具链回退到stringstream。 - spdlog:公共头文件把格式化库抽象成
fmt_lib别名,std::format后端可用时将格式化字符串类型统一到std::format_string,调用方不必区分后端。 - 与
std::print的分工:C++23 的std::print直接写标准输出;需要先构造字符串或写入其他目标时,仍用std::format与std::format_to。
横向对比¶
| 方案 | 类型安全 | 编译期检查 | 返回字符串 | 主要代价 |
|---|---|---|---|---|
printf |
否 | 否 | 否 | 格式串与实参分离,错误是未定义行为 |
iostream |
是 | 否 | 否 | 流状态有粘性,格式化代码冗长 |
std::format |
是 | 是(字面量) | 是 | C++20 支持较新;自定义类型需 formatter |
fmt::format |
是 | 是 | 是 | 第三方依赖;扩展点更多,语法略有差异 |
std::to_chars |
是 | — | 否 | 只覆盖数值转换,不做通用文本拼接 |
跨标准演进:C++20 引入 std::format;C++23 增加 std::print、std::println 和更多格式化器;C++26 继续扩展格式语法与范围支持。std::format 与 fmt 共享设计血统,但标准版本的功能集合更保守。