GitHub 趋势 · 第 31 期

std::format 都进标准了,25,718 star 的 fmt 还在涨什么?

#C++#基础库#MIT

我的判断:fmt 值得引,但理由不是「它比 std::format 更好」,而是——你手上的工具链大概率还没吃上 std::format,而 fmt 正是那份标准的事实来源。README 的第一条 Feature 就写着:它是 C++20 std::format 与 C++23 std::print 的实现。换句话说,标准里那套格式串语法,本来就是从这里长出来的。而这个「老库」最近一次提交是 2026-09-09,12.0 把 double 格式化做到比 11.2 快 1.64 倍,12.2 又加了 C11 版本的 API——它没有停在「标准采纳了我」这件事上。

痛点场景:C++ 里「把几个变量拼成一行字符串」这件小事,长期以来只有两条路,两条都有坑。

走 iostreams:std::cout << "user=" << id << " took " << ms << "ms",格式和数据混在一条链上,改一次顺序要数半天。更麻烦的是流上那些粘性状态——std::setprecisionstd::hex 设一次会影响后面所有输出。

走 printf:类型不安全,格式串和实参对不上编译器未必拦你,跑起来才知道。而且这两条路在编译时间和二进制体积上都不便宜——README 里官方跑的那份基准(100 个 translation unit、每个用 5 次格式化,模拟中型工程,Apple M5 Max / Apple Clang 21)显示:iostreams 编译要 25.5 秒、产物 98 KiB;fmt 12.2 只要 5.1 秒、54 KiB

项目是什么

fmtlib/fmt,官方一句话定位很朴素:an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams——C 的 stdio 与 C++ iostreams 的一个又快又安全的替代品。作者 Victor Zverovich,许可证是 MIT,仓库到现在仍然活跃。

它最容易被低估的地方在于「身份」:C++20 的 std::format 与 C++23 的 std::print 都源自这个库,README 把它列在 Features 第一条。所以当你在纠结「标准里已经有了,还要不要引第三方」时,实际问的是另一个问题:你现在这套编译器 + 标准库,std::format 到底能不能用、好不好用。

使用方名单能把这件事说透:Apple 的 FoundationDB、Blizzard Battle.net、Ceph、ClickHouse、Envoy、Folly、MariaDB、MongoDB、PyTorch、Seastar、spdlog、Windows Terminal。这不是一个「新玩具」,是一层很多人天天在用、但通常意识不到的基础设施。最新发行版 12.2.0(2026-06-16),仓库最近一次提交 2026-09-09。

核心能力

下面这份清单直接取自 README 的 Features 一节,我只做了归类:

能力README 原文要点
格式 API简洁的 format API,带位置参数(positional arguments),为本地化做准备
与标准同源实现了 C++20 std::format 与 C++23 std::print
格式串语法类似 Python 的 str.format 语法,学过 Python 就能上手
浮点格式化IEEE 754 正确舍入,保证 shortest(最短表示)与 round-trip(可回读),底层用 Dragonbox 算法
Unicode可移植的 Unicode 支持
安全 printf类型安全的 printf 实现,含 POSIX 的位置参数扩展
可扩展支持用户自定义类型的格式化(自己写 formatter)
性能快于常见实现的 (s)printf、iostreams、to_string 与 to_chars;可把格式串编译成高效的格式化代码
体积最小配置只要三个文件:core.h、format.h、format-inl.h;源码与生成的代码都小
安全与可靠完全类型安全,格式串错误能在编译期报出;自动内存管理避免缓冲区溢出;测试覆盖广,并在 oss-fuzz 上持续模糊测试
易用自包含、无外部依赖、许可宽松(MIT)
可移植跨平台输出一致,且支持较老的编译器
编译器友好在 -Wall -Wextra -pedantic 这种高警告级别下依然干净无警告
Locale默认与 locale 无关,输出不会被环境影响
Header-only可选,用 FMT_HEADER_ONLY 宏打开

12.x 这一代又补了几件事,都写在发行说明里:12.0 让 double 的格式化比 11.2 快了 60% 以上(dtoa-benchmark 上 34.471 ns 降到 21.000 ns,1.64 倍),并给 fmt::format 加上了 constexpr 支持、新增 FMT_STATIC_FORMAT(编译期就按需把结果定长展开成字符串);12.2 默认启用完整的 Dragonbox 查表缓存,再快 10% 到 25%,还新增了一套 C11 的 API(fmt-c 库与 fmt/fmt-c.h,用 _Generic 做类型分发),官方说明是性能优于 printf/sprintf。一个 25k star 的库,还在往 C 语言方向长,这个细节挺能说明它的性格。

上手

用法有多直接?下面是 README 里的例子,一个字没改:

#include <fmt/core.h>

int main() {
  fmt::print("Hello, world!\n");
}

std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."

std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."

README 还给了几个能立刻看出和 iostreams 差距的例子:时间格式化(fmt/chrono.h)、容器直接打印(fmt/ranges.h,一个 vector 直接输出 [1, 2, 3])、单线程写文件(fmt/os.h)、带颜色和样式的终端输出(fmt/color.h,README 的示例里连「你好世界」都有)。以及最能说明类型安全的一点——下面这行在 C++20 下会直接编译不过,因为 d 对 string 是非法格式说明符:

std::string s = fmt::format("{:d}", "I am not a number");

接入方式(取自官方文档 Get Started 一节,README 本身不含安装章节)。CMake 项目三条路,最省事的是 FetchContent:

include(FetchContent)

FetchContent_Declare(
  fmt
  GIT_REPOSITORY https://github.com/fmtlib/fmt
  GIT_TAG        e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1
FetchContent_MakeAvailable(fmt)

target_link_libraries(<your-target> fmt::fmt)

已经装好了就用两行;把源码树搬进自己项目就直接 add_subdirectory。库提供两个 target:fmt::fmt(编译库)与 fmt::fmt-header-only,官方建议用编译库,编译更快。不想碰 CMake 的话,包管理器直接装:

apt install libfmt-dev                                  # Debian / Ubuntu
brew install fmt                                        # macOS
conda install -c conda-forge fmt                        # conda-forge
./vcpkg install fmt                                     # vcpkg
conan install -r conancenter --requires="fmt/[*]" --build=missing   # Conan

从源码构建就是标准 CMake 三步;其他构建系统文档里也有现成写法(build2、Meson、Android NDK)。最原始的方式是:把 include/fmt/base.h、include/fmt/format.h、include/fmt/format-inl.h 和 src/format.cc 拖进你的工程,把 include 加进头文件搜索路径,确保 src/format.cc 被编译并链接。

我的判断

适合谁:还在用 iostreams 拼日志或 printf 拼消息的 C++ 项目——迁移成本低、收益(编译时间、体积、类型安全)直接可见;编译器或标准库版本偏旧、std::format 支持不完整甚至没有的团队;想用 fmt::print 那套开箱能力(颜色与文字样式、output_file、chrono、容器直接打印)的人;以及需要在编译期就把格式串错误拦下来、不想等到运行时才炸的工程。

不适合谁:已经全线 C++23、工具链最新、且只在少数几处做简单字符串拼接的项目——这种情况下标准库够用,多一个依赖就是多一份维护面;以及团队的第三方依赖政策严格收紧、引入任何新依赖都要走评审的环境。另外,如果你的项目对 ABI 极度敏感、库里已经因为别的组件混进了 fmt 的旧版本,先查版本冲突再谈引入。

几个坑(全部来自 12.x 发行说明、README 或 LICENSE 原文):

  • 升级到 12.2 最容易踩的坑:<fmt/core.h> 换了含义。12.2.0 的发行说明写明,<fmt/core.h> 现在默认等同于 <fmt/base.h>。以前靠它顺带把 <fmt/format.h> 拉进来的代码,现在要么显式 include <fmt/format.h>,要么定义 FMT_DEPRECATED_HEAVY_CORE 回到旧行为。升级后突然一片编译错误的,先看这条。
  • 12.0.0 删掉了一批早已弃用的 API。发行说明列得很清楚:has_formatter(改用 is_formattable)、fmt::localtime、宽字符版本的 fmt::printf 重载、带文字样式的宽字符 print 重载、以及一批 is_*char traits;此外宏 FMT_EXCEPTIONS 改名为 FMT_USE_EXCEPTIONS。跨大版本升,务必先读一遍发行说明。
  • 12.2.0 又新弃用了一批:fmt::format_string / basic_fstring 到 string_view 的隐式转换(改用 format_string::get())、fmt::join 的 initializer_list 重载、vformat_to 的数组重载;fmt::say 被删除;std::byte 的 formatter 从 fmt/format.h 挪到了 fmt/std.h。都是「编译通过但行为变了」这一类,值得扫一遍。
  • 别把「和标准同源」读成「可以和 std::format 随便混用」。两者终究是两套实现、两套类型与错误处理路径。同一个项目里既写 std::formatter 特化又写 fmt::formatter 特化,认知成本和维护成本都会上去。选一条路走到底,别两边都留一半。
  • 关键路径集中在一人身上。贡献者列表里绝大多数提交来自作者 Victor Zverovich 一人,这不是「没人维护」,而是「关键路径很集中」。真正托底的是生态——FoundationDB、PyTorch、ClickHouse、MongoDB、Envoy、spdlog 这些项目在用,意味着它不会轻易停摆;但按大厂项目的响应预期去等 issue 排期,也不现实。
  • 性能数字要看口径。README 说的是「比 sprintf 与 iostreams 快几十个百分点到 20 至 30 倍」,那是相对关系;12.0 那个 1.64 倍是 double 格式化、开了格式串编译、写进栈上 buffer 的特定场景。基准脚本(format-benchmark / dtoa-benchmark)和机器配置都公开,套到自己项目上之前,最好照着自己的真实调用跑一遍。

仓库:github.com/fmtlib/fmt 文档:fmt.dev

总 star:25,718(截至 2026-09-11,本周 +1,847);fork 3,057

许可证:MIT(Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors)

主语言:C++;最新发行版 12.2.0(2026-06-16),最近提交 2026-09-09

接入:CMake FetchContent / find_package / add_subdirectory;apt / brew / conda / vcpkg / conan

数据来源:GitHub Trending 官方页面 + 仓库 README / LICENSE / 发行说明 / 官方文档 Get Started

抓取时间:2026-09-11 04:37(GMT+8)

顺便问一句:你手上正在维护的 C++ 项目,格式化这一层现在是 iostreams、printf,还是已经换成了 std::format 或 fmt?如果是 fmt,你是在哪个版本迁进去的——欢迎在评论区说说你踩过的那个「编译突然不过了」。

下期预告:继续留在「基础设施」这条线上——openai/plugins(6,378 star,本周 +753),本周榜上为数不多我还没写过、也不属于 AI 技能类的仓库。它是官方插件仓库,我会先去核实它到底提供什么;如果内容确实太薄,就换榜上的其他项目,不硬凑一期。

#C++#基础库#MIT