如何在自定义std::formatter中实现自定义解析且不丢失标准格式化功能
如何在自定义std::formatter中实现自定义解析且不丢失标准格式化功能
兄弟,我看了你写的自定义std::formatter代码,问题核心在于你没有让基类formatter正确解析标准格式说明符,反而自己手动遍历格式上下文,导致基类完全不知道你设置的宽度、对齐这些参数,自然没法应用这些标准格式化规则。
先说说你代码里的问题点:
- 你的
parse函数手动处理了整个格式字符串,完全没调用基类的parse方法,基类formatter<string_view>的内部状态(比如对齐方式、宽度)根本没被初始化,后面调用base::format时,这些标准格式参数等于没生效。 - 手动维护
bracket_count的逻辑也是多余的,format_parse_context本身已经帮你处理了嵌套括号的情况,不需要自己折腾。
正确的实现思路和代码
正确的做法是:让基类先处理它能识别的标准格式部分(对齐、宽度等),再解析你的自定义标记(比如M),这样基类就能保留标准格式的状态,后续格式化时自动应用这些规则。
修改后的完整代码如下:
#include <iostream> #include <format> #include <string> #include <iterator> using namespace std; struct type_a { int val; }; template<> struct std::formatter<type_a> : formatter<string_view> { using base = formatter<string_view>; // 自定义标记:是否输出罗马数字(移到结构体内部,避免全局变量的线程安全问题) bool roman_numerals = false; constexpr auto parse(format_parse_context& parse_ctx) { // 第一步:让基类解析标准格式说明符(比如>10的对齐和宽度) auto pos = base::parse(parse_ctx); // 第二步:检查剩下的字符是否是自定义标记'M' if (pos != parse_ctx.end() && *pos == 'M') { roman_numerals = true; ++pos; } // 返回格式字符串的结束位置 return pos; } template<typename FormatContext> auto format(const type_a& obj, FormatContext& format_ctx) const { // 生成要格式化的内容:普通数字字符串或罗马数字字符串 string content; if (roman_numerals) { // 保留你的罗马数字转换逻辑 const string romans[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; const int values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; int val = obj.val; while (val > 0) { for (size_t i = 0; i < 13; ++i) { if (val >= values[i]) { content += romans[i]; val -= values[i]; break; } } } } else { content = to_string(obj.val); } // 用基类的format方法,它已经保存了标准格式参数,会自动应用对齐、宽度等规则 return base::format(content, format_ctx); } }; int main() { // Runtime parsed. cout << vformat("The number '{0:>10}' as roman numerals '{1:>10}' \n", make_format_args(123, "CXXIII")); cout << vformat("The number '{0:>10}' as roman numerals '{0:>10M}' \n", make_format_args(type_a{123})); // Compile time parsed cout << format("The number '{0:>10}' as roman numerals '{0:>10M}' \n", type_a{123}); }
关键修改点说明:
parse函数的正确逻辑:- 先调用
base::parse(parse_ctx),让基类自动处理标准格式说明符(对齐、宽度等),并把这些参数保存到内部状态中。 - 之后检查剩余字符是否是自定义标记
M,如果是则设置roman_numerals标志,不需要手动处理括号嵌套。
- 先调用
format函数的优化:- 先根据标记生成要格式化的内容(数字或罗马字符串),再交给基类的
format方法处理,基类会自动应用之前解析到的标准格式规则,比如右对齐、宽度10的效果就会正常生效了。
- 先根据标记生成要格式化的内容(数字或罗马字符串),再交给基类的
变量作用域修正:
- 把
roman_numerals移到结构体内部,避免全局变量带来的线程安全问题,也符合std::formatter的设计规范(每个formatter实例应该有独立的状态)。
- 把
运行这段代码后,你会发现>10的右对齐和宽度设置完全生效,罗马数字和普通数字都会按照指定的格式输出。
备注:内容来源于stack exchange,提问作者Generic Name
相关产品推荐
相关产品推荐

