跳转至

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 共享设计血统,但标准版本的功能集合更保守。