如何在用户定义的 std::formatter 中进行自定义解析而不丢失标准格式

问题描述 投票:0回答:1

我正在尝试使用自定义格式说明符为自定义类型创建一个 std::formatter 。 我不知道如何在不丢失格式库中完成的所有标准格式的情况下执行此操作。

这是一个示例,我将用户类型“type_a”格式化为罗马数字。

#include <iostream>
#include <format>
using namespace std;

struct type_a { int val; };

template<>
struct std::formatter<type_a> : formatter<string_view> {
    using base = formatter<string_view>;

    // How do I take out my special formatting characters and get the rest parsed by std::something?
    constexpr auto parse( format_parse_context & parse_ctx ) {
        auto pos = parse_ctx.begin();
        int bracket_count = 1;
        while( pos != parse_ctx.end() && bracket_count > 0 ) {
            if( *pos == '{' )
                ++bracket_count;
            else if( *pos == '}' )
                --bracket_count;
            else if( *pos == 'M' ) // Output as roman numerals
                roman_numerals = true;
            ++pos;
        }
        while( pos != parse_ctx.end() && bracket_count > 0 ) {
            if( *pos == '{' )
                ++bracket_count;
            else if( *pos == '}' )
                --bracket_count;
            ++pos;
        }
        --pos;
        return pos; // pos points to the last bracket.
    }

    template<template<typename, typename> typename Basic_Format_Context, typename Output_Iterator, typename CharT>
    auto format( const type_a & obj, Basic_Format_Context<Output_Iterator, CharT> & format_ctx ) const {
        auto roman_string = []( int val ) {
            string romans[] = {   "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
            int values[]    = { 1000,  900, 500,  400, 100,   90,  50,   40,  10,    9,   5,    4,   1  };
            string result;
            for( int i = 0; i < 13; ++i ) {
                while( val - values[i] >= 0 ) {
                    result += romans[i];
                    val    -= values[i];
                }
            }
            return result;
        };
        string str;
        if( roman_numerals )
            format_to( back_inserter( str ), "{}", roman_string( obj.val ) );
        else
            format_to( back_inserter( str ), "{}", obj.val );
        return base::format( str, format_ctx );
    }    

    bool roman_numerals = false;
};

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 } );
}

我失去了指定的宽度和输出的合理性。

我怎样才能制作这样的

formatter
并保留所有标准格式?

https://godbolt.org/z/nGcT3aKPc

c++ c++20 fmt stdformat
1个回答
0
投票

如何制作这样的格式化程序并保留所有标准格式?

我认为您可以将罗马数字说明符放在标准规范之前,并通过检测其存在来设置

roman_numerals

这允许您重用标准格式化程序。以指标

"roman"
为例:

template<>
struct std::formatter<type_a> {
  std::formatter<string_view> underlying;
  bool roman_numerals = false;

  constexpr auto parse(std::format_parse_context& parse_ctx) {
    if (std::string_view(parse_ctx).starts_with("roman")) {
      roman_numerals = true;
      parse_ctx.advance_to(parse_ctx.begin() + 5);
    }
    return underlying.parse(parse_ctx);
  }
   
  auto format(const type_a& obj, std::format_context& format_ctx) const {
    auto roman_string = [](int val) { /* implementation */ };
    auto str = roman_numerals ? roman_string(obj.val)
                              : std::to_string(obj.val);
    return underlying.format(str, format_ctx);
  }
};

然后你就可以像这样使用它了

std::cout << std::format("The number '{0:>10}' as roman numerals '{1:roman>10}' \n", 123, type_a{123});

演示

© www.soinside.com 2019 - 2024. All rights reserved.