I have a header that defines all {fmt} formatters for my custom types.
To improve compile times, I wanted to reduce the dependencies of this custom formatter header, and decided to define all formatters as external templates, where the implementation is put in a .cpp, with the declarations in the header file as follows:
template<>
struct formatter<MyType> : formatter<std::string>
{
auto format(const MyType& t, format_context& ctx);
};
extern template struct formatter<MyType>;
...and the definitions in the .cpp file:
auto formatter<MyType>::format(const MyType& t, format_context& ctx)
{
return format_to(ctx.out, "MyType: {}", ...);
}
The main advantage is that the header file becomes much less heavy, all custom types can be forward declared and I no longer include the world in case I want to have custom formatting of a single type within some translation unit.
However, most of the examples on implementing custom formatters with {fmt} define the format() function as a template function templatized on the format_context type:
template<typename FormatContext>
auto format(const MyType& t, FormatContext& ctx);
This does not really work with external templates since I would need to declare format() for all possible types of FormatContext up front. This is error prone. For now, only using fmt::format_context works and the compiler will tell me when it no longer suffices.
I would like to know what I lose by not having a format function templatized on the FormatContext type? In what situations is fmt::format_context not sufficient? Is there a better way to define these custom type formatters without having to put the complete implementation in header files? I was thinking about going the std::ostream route, and then simply including <fmt/ostream.h> whenever I would want to format my types with {fmt}, but it partially defeats the purpose of using {fmt} in the first place.