背景与目标
现代 C++ 提供了 std::format
,比传统的 std::stringstream
更安全、高效,也避免了手动拼接。但对于旧代码或者希望“自动推导格式串”的开发者来说,迁移成本高。
我有一个常用的辅助函数 inner_string(...)
,基于 stringstream
构建字符串拼接,现在我希望用 std::format
替代它,并自动推导 {}
占位符的数量与格式。
目标是:
- 保留调用方式不变(如
inner_string(1.23, "abc", 42)
)
- 自动生成合适的
format_string
结构
- 对于 float/double 自动加
{:.2f}
之类的精度
- 支持
tuple
等复合类型拆解
- 最好在编译期完成这些逻辑
旧代码方式回顾:stream 拼接
我们之前常用 stringstream
实现类似 print
效果:
1 2
| std::stringstream ss; ss << 123 << "abc" << 1.9 << '\n';
|
封装过后是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template<typename _Func, typename..._Args> static void _inner_unpak(_Func fun, const _Args&... args) { (void)std::initializer_list<int>{ [&](const auto& arg) { fun(arg); return 0; }(args)... }; }
template<typename..._Args> inline static void _inner_format(std::ostream& stream, const _Args&... args) { _inner_unpak([&](const auto& arg) { stream << arg; }, args...); }
template<typename... _Args> inline static std::string inner_string(const _Args&... args) { std::stringstream ss; _inner_format(ss, args...); return ss.str(); }
|
这其实已经是很泛化的流拼接工具了,和 std::format
的格式感知能力相比较,缺点是:
- 不支持精度控制
- 输出不统一
- 类型错误编译期无法发现
为了接近 std::format
的风格,第一步尝试是:根据参数个数生成一串 {}
占位符。
1 2 3 4 5 6 7 8 9 10
| template<typename... Args> std::string format_string(const Args&... args) { std::size_t arg_count = sizeof...(args); std::string placeholders; for (std::size_t i = 0; i < arg_count; ++i) { placeholders += "{}"; if (i < arg_count - 1) placeholders += ' '; } return std::vformat(placeholders, std::make_format_args(args...)); }
|
但是存在几个问题:
- 每次都运行时构造格式字符串,效率低
- 无法针对类型生成特定格式(如 float 想输出
{:.2f}
)
- 不支持结构体 / tuple 等更复杂类型
编译期自动推导格式字符串:终极方案
我设计了一个完全在编译期推导格式串的系统,基于:
consteval
编译期函数
concepts
判断是否是 tuple
- 模板参数展开
std::array<char>
拼接字符串
- C++20
string_view
缓存格式串
类型到格式映射机制
1 2 3 4 5
| template <typename T> struct type_format { static constexpr const char* value = "{}"; };
template <> struct type_format<float> { static constexpr const char* value = "{:.2f}"; }; template <> struct type_format<double> { static constexpr const char* value = "{:.2f}"; };
|
判断是否 tuple 类型
1 2
| template <typename T> concept is_tuple_like = requires { typename std::tuple_size<T>::type; };
|
递归收集格式
1 2 3 4 5 6 7 8 9 10
| template <typename T, typename Collector> consteval void collect_type_formats(Collector collector) { if constexpr (is_tuple_like<T>) { [&]<std::size_t... Is>(std::index_sequence<Is...>) { (collect_type_formats<std::tuple_element_t<Is, T>>(collector), ...); }(std::make_index_sequence<std::tuple_size_v<T>>{}); } else { collector(type_format<std::decay_t<T>>::value); } }
|
拼接格式串(含分隔符)
1 2 3 4 5 6 7 8 9 10 11 12 13
| template <std::size_t Count, char Sep = ' '> consteval auto make_format_string(const std::array<const char*, Count>& formats, std::size_t used) { std::array<char, AUTO_FORMAT_LOGGER_MAX_ARGS * 16> buf{}; std::size_t pos = 0; for (std::size_t i = 0; i < used; ++i) { const char* f = formats[i]; while (*f) buf[pos++] = *f++; if (i < used - 1) buf[pos++] = Sep; } std::array<char, AUTO_FORMAT_LOGGER_MAX_ARGS * 16> result{}; std::copy_n(buf.begin(), pos, result.begin()); return std::pair{result, pos}; }
|
外部统一封装接口
1 2 3 4 5
| template <typename... Args> struct type_format_string_placeholders { static constexpr auto fmt = make_type_format_string_ct<Args...>(); static constexpr auto sv = std::string_view{fmt.first.data(), fmt.second}; };
|
使用示例
1 2 3 4 5 6 7
| constexpr auto fmt = auto_format_rules::detail::make_type_format_string_ct<float, int, float>(); std::string_view sv(fmt.first.data(), fmt.second);
float a = 1.2345f, c = 3.14f; int b = 42; std::string result = std::vformat(sv, std::make_format_args(a, b, c));
|
也支持 tuple 嵌套:
1 2
| using tup = std::tuple<int, float, std::tuple<double>>; constexpr auto sv = auto_format_rules::detail::type_format_string_placeholders<tup>::sv;
|
编译期优势对比
方法 |
编译期 |
类型感知 |
精度控制 |
性能开销 |
手动写格式串 |
❌ |
❌ |
❌ |
最低 |
运行时拼接 {} |
❌ |
❌ |
❌ |
中 |
本文方案:类型推导 + 缓存 |
✅ |
✅ |
✅ |
最优 |
后续扩展想法
{name}
风格格式串支持(需配合索引与映射)
- 自动为 struct 提供格式(结合 C++23
reflexpr
)
- 支持单位后缀拼接(如
KB
、ms
)
- 编译期格式串验证与调试输出
总结
这个系统完全基于编译期的 constexpr + 模板机制实现,支持自动生成符合类型的格式字符串,并为日志、调试、格式输出等提供了更高的性能与更好的类型安全。
欢迎留言交流优化建议或 fork 后自定义扩展。