You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在自定义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});
}

关键修改点说明:

  1. parse函数的正确逻辑:

    • 先调用base::parse(parse_ctx),让基类自动处理标准格式说明符(对齐、宽度等),并把这些参数保存到内部状态中。
    • 之后检查剩余字符是否是自定义标记M,如果是则设置roman_numerals标志,不需要手动处理括号嵌套。
  2. format函数的优化:

    • 先根据标记生成要格式化的内容(数字或罗马字符串),再交给基类的format方法处理,基类会自动应用之前解析到的标准格式规则,比如右对齐、宽度10的效果就会正常生效了。
  3. 变量作用域修正:

    • 把roman_numerals移到结构体内部,避免全局变量带来的线程安全问题,也符合std::formatter的设计规范(每个formatter实例应该有独立的状态)。

运行这段代码后,你会发现>10的右对齐和宽度设置完全生效,罗马数字和普通数字都会按照指定的格式输出。

备注:内容来源于stack exchange,提问作者Generic Name

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 10:33:03