I am trying to make a std::formatter for a custom type with custom format specifiers. I cannot figure out how to do this without loosing all the standard formatting done in the formatting library.
Here is an example where I format a user type 'type_a' as roman numerals.
#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 } );
}
I am loosing the width I specify and the justification of the output.
How can I make such a formatter
and keep all the standard formatting?
https://godbolt.org/z/nGcT3aKPc
Thanks in advance.
I think you can put the Roman numeral specifier before the standard spec and set
roman_numerals
by detecting its presence.This allows you to reuse the standard formatter. Take the indicator
"roman"
as an example:Then you can use it like
Demo