TD22057
TD22057

Reputation: 11

C++: overloading builtin (double) formatter in fmt lib

I'm using the fmt library in C++ (c++-17). In this particular library, values with unit types (angles, lengths, etc) are stored as double's in a set of defined units (rad, km, etc). In various printing/logging functions, they get printed as a unit type where the user has control over the output format (print all angles as degrees). Currently we do something like this:

double value = 0.1234;
out << "angle = " << Unit::format( value, Unit::angle ) << "\n";

I'd like to use the fmt library to do the formatting by extending the formatter for double to understand some custom formatting codes. For example, I'd like to say:

out << fmt::format( "angle = {:.3f[angle]}\n", value ); 

and have the custom formatter parse the angle spec and do any unit conversions before passing through to the regular double formatter. I've tried following the custom type docs here and using fmt::formatter<double> but it doesn't appear to ever run that code much less allow me to parse do some parsing of the format and then pass through to the "real" double formatter. Running something like the following compiles normally and just outputs the standard double format (this is just a quick hack/test to see if I could get double to run through my code). The custom struct formatter does call through to new double formatter, but the double variable does not.

Any ideas on how to override/extend the default formatter object for double?

#include <iostream>
#include <fmt/format.h>
#include <fmt/core.h>

struct Foo { double a; };

template <> struct fmt::formatter<double> {
  constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
     return ctx.begin();
  }
     
  template <typename FormatContext>
  auto format(const double& v, FormatContext& ctx) -> decltype(ctx.out()) {
     std::cerr << "formatter<double>::format\n";
     return format_to( ctx.out(), "custom: {}", v );
  }   
};

template <> struct fmt::formatter<Foo> : fmt::formatter<double> {
  template <typename FormatContext>
  auto format(const Foo& v, FormatContext& ctx) -> decltype(ctx.out()) {
     std::cerr << "formatter<Foo>::format\n";
     return formatter<double>::format(v.a, ctx );
  }   
};

int main()
{
   Foo a;
   a.a = 1.12345;
   std::cout << fmt::format( "Foo : {}\n", a );

   double b = 1.2345;
   std::cout << fmt::format( "dbl : {}\n", b );
   
   return 0;
}

Yields:

formatter<foo>::format
formatter<double>::format
foo : custom: 1.12345
dbl : 1.2345

Upvotes: 1

Views: 1200

Answers (1)

vitaut
vitaut

Reputation: 55594

You cannot override formatting of built-in types but you can create a new type that represents an angle or any other unit and make it formattable:

struct Angle {
  double value;
};


template <> struct fmt::formatter<Angle> : fmt::formatter<double> {
  auto format(Angle angle, format_context& ctx) {
    // Implement custom formatting here.
    return ctx.out();
  }   
};

std::string s = fmt::format("{}", Angle{42});

Upvotes: 1

Related Questions