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

C++ {fmt}库:如何利用内置格式解析输出自定义有理数类型?

C++ {fmt}库实现有理数类型格式化的问题与解决方案

问题描述

我有一个带分子、分母的模板化有理数类型,正在通过特化fmt::formatter实现格式化。核心需求:

  • 复用整数类型的所有格式规范,不用自己写解析代码
  • 分母为1时只输出分子,否则输出分子/分母格式
  • 格式字符串指定的宽度要作用于整个输出内容,而非仅分子

目前的代码实现了基础输出,但格式规范只对分子生效,无法让宽度作用于整体,现有代码如下:

template<rational::Rational T> struct fmt::formatter<T>
    : fmt::formatter<typename T::numerator_type> {
    using base_type = fmt::formatter<typename T::numerator_type>;;
    template<class FormatContext>
    auto format(const T& r, FormatContext& ctx) {
        auto n = r.num();
        auto d = r.den();
        if (d == 1)
            return base_type::format(n, ctx);

        base_type::format(n, ctx);
        return format_to(ctx.out(), "/{}", d);
    }
};

解决方案

直接分两次输出分子和分母的方式,会让基类的宽度规范只作用在分子上,所以需要换思路处理:要么先构造完整的输出字符串再应用格式,要么手动利用基类解析的格式参数处理整体宽度。

方法1:构造完整字符串后应用格式(简洁首选)

先把有理数转换成完整的字符串(p/q或p),再让基类的格式化逻辑作用于这个字符串,这样所有格式规范(包括宽度、对齐)都会应用到整体内容上:

#include <fmt/format.h>
#include <string>

template<rational::Rational T> struct fmt::formatter<T>
    : fmt::formatter<typename T::numerator_type> {
    using base_type = fmt::formatter<typename T::numerator_type>;
    
    template<class FormatContext>
    auto format(const T& r, FormatContext& ctx) {
        const auto n = r.num();
        const auto d = r.den();
        const std::string output_str = (d == 1) 
            ? fmt::format("{}", n) 
            : fmt::format("{}/{}", n, d);
        
        // 让基类处理整个字符串的格式化,自动应用所有解析好的规范
        return base_type::format(output_str, ctx);
    }
};

方法2:手动处理宽度与对齐(高效无中间字符串)

如果不想生成中间字符串,可以先计算完整输出的长度,再根据基类解析出的宽度、对齐参数手动处理填充:

#include <fmt/format.h>

template<rational::Rational T> struct fmt::formatter<T>
    : fmt::formatter<typename T::numerator_type> {
    using base_type = fmt::formatter<typename T::numerator_type>;
    
    template<class FormatContext>
    auto format(const T& r, FormatContext& ctx) {
        const auto n = r.num();
        const auto d = r.den();
        const std::string_view fmt_pattern = (d == 1) ? "{}" : "{}/{}";
        
        // 计算未填充时的输出长度
        const auto unpadded_length = fmt::format_to_n(nullptr, 0, fmt_pattern, n, d).size;
        const auto specified_width = this->width;

        if (specified_width > unpadded_length) {
            const auto padding_length = specified_width - unpadded_length;
            // 根据基类解析的对齐方式处理填充
            if (this->align == fmt::alignment::left) {
                // 先输出内容,再补填充字符
                fmt::format_to(ctx.out(), fmt_pattern, n, d);
                return fmt::format_to(ctx.out(), "{:>{}}", "", padding_length);
            } else {
                // 先补填充字符,再输出内容
                fmt::format_to(ctx.out(), "{:>{}}", "", padding_length);
                return fmt::format_to(ctx.out(), fmt_pattern, n, d);
            }
        } else {
            // 宽度不足,直接输出完整内容
            return fmt::format_to(ctx.out(), fmt_pattern, n, d);
        }
    }
};

关键注意点

  • 继承fmt::formatter<typename T::numerator_type>后,基类的parse方法会自动处理所有整数格式规范(宽度、对齐、进制、填充字符等),不用自己写解析逻辑
  • 方法1更简洁,完全复用基类的格式化能力,不容易出错
  • 方法2避免了中间字符串的内存开销,但需要手动处理对齐逻辑,依赖fmt库formatter的公开成员(比如width、align),兼容性没问题

内容的提问来源于stack exchange,提问作者RandomBits

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 14:02:50