C : перегрузка встроенного (двойного) средства форматирования в fmt lib

#c #fmt

Вопрос:

Я использую библиотеку fmt на C (c -17). В этой конкретной библиотеке значения с типами единиц измерения (углы, длины и т.д.) хранятся как двойные в наборе определенных единиц измерения (рад, км и т.д.). В различных функциях печати / ведения журнала они печатаются как тип единицы измерения, где пользователь имеет контроль над форматом вывода (печать всех углов в градусах). В настоящее время мы делаем что-то вроде этого:

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

Я хотел бы использовать библиотеку fmt для форматирования, расширив средство форматирования для double, чтобы понять некоторые пользовательские коды форматирования. Например, я хотел бы сказать:

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

и пусть пользовательский форматировщик проанализирует спецификацию angle и выполнит любые преобразования единиц измерения, прежде чем перейти к обычному двойному форматированию. Я пытался следовать документам пользовательского типа здесь и использовать fmt::formatter<double> , но, похоже, он никогда не запускает этот код, а тем более не позволяет мне анализировать, выполнять некоторый синтаксический анализ формата, а затем переходить к «реальному» двойному форматированию. Выполнение чего-то вроде следующего компилируется нормально и просто выводит стандартный формат double (это просто быстрый взлом / тест, чтобы увидеть, смогу ли я заставить double работать через мой код). Пользовательский форматировщик структуры вызывает новый форматировщик double, но переменная double этого не делает.

Есть идеи о том, как переопределить / расширить объект форматирования по умолчанию для 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_contextamp; ctx) -> decltype(ctx.begin()) {
     return ctx.begin();
  }
     
  template <typename FormatContext>
  auto format(const doubleamp; v, FormatContextamp; ctx) -> decltype(ctx.out()) {
     std::cerr << "formatter<double>::formatn";
     return format_to( ctx.out(), "custom: {}", v );
  }   
};

template <> struct fmt::formatter<Foo> : fmt::formatter<double> {
  template <typename FormatContext>
  auto format(const Fooamp; v, FormatContextamp; ctx) -> decltype(ctx.out()) {
     std::cerr << "formatter<Foo>::formatn";
     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;
}
 

Дает:

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

Комментарии:

1. Может быть, пойти на fmt::format("{}", Angle(42)) ? Должно быть проще.

2. Да, спасибо. Это мой запасной вариант. трудно иметь один тип для unit, поскольку любая комбинация степеней длины, времени, массы и т. Д. Может быть единицей. Таким образом, вызывающий seq для этого get в некоторых случаях длинный.

Ответ №1:

Вы не можете переопределить форматирование встроенных типов, но вы можете создать новый тип, представляющий угол или любую другую единицу, и сделать его форматируемым:

 struct Angle {
  double value;
};


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

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