I am trying to write a custom formatter to help me print vectors. I am attempting to maintain the format specifiers so that each item in the vector is formatted the same way.
I have taken most of my inspiration from this tutorial, and the docs
#include <fmt/core.h>
#include <fmt/format.h>
template<typename ValueType>
struct fmt::formatter<std::vector<ValueType>> : fmt::formatter<ValueType>
{
std::string formatString;
// Hack, copy the original format text into a std::string
constexpr auto parse(format_parse_context& ctx)
{
formatString= "{:";
for (auto iter = std::begin(ctx); iter != std::end(ctx); ++iter) {
char c = *iter;{
formatString += c;
}
if (c == '}') {
return iter;
}
}
return std::end(ctx);
}
template <typename FormatContext>
auto format(const std::vector<ValueType>& container, FormatContext& context)
{
auto&& out = context.out();
format_to(out, "{{");
typename std::vector<ValueType>::size_type count = 0;
const typename std::vector<ValueType>::size_type size = container.size();
for (const auto& item : container) {
// Use the copied format string, but really want to delegate the formatting to superclass...
format_to(out, formatString, item);
if (++count < size) {
format_to(out, ", ");
}
}
return format_to(out, "}}");
}
};
int main()
{
fmt::print("{:.3f}\n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
fmt::print("{:.1e}\n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
return 0;
}
Which outputs:
{0.000, 321.123, 654398.433, -0.000, 2374651273.724}
{0.0e+00, 3.2e+02, 6.5e+05, -1.2e-12, 2.4e+09}
It seems overly clunky and inefficient to copy the format string just so I can feed it back into another fmt::format call, especially when the extended class : fmt::formatter<ValueType> is already providing us with a perfectly valid parse function internally, (which I have re-implemented in this example just to get the desired output in a hacky way).
I really want to remove the custom parse implementation and replace the line
format_to(out, formatString, item);
with
format_to(out, fmt::formatter<ValueType>::format(item, context))
except it isn't valid/doesn't compile.
What is the correct way to do this?
Note: I am completely aware of the pointlessness of extending the type in my example, and that I could have it as a local variable however I am trying to reuse the functionality of the class, so extending it feels like the right direction, even if I haven't found the solution yet.
A list of all the other examples I have found which haven't helped me yet:
- String formatting the cool way with C++20 std::format() (only works for directly returning a single call to the super implementation, but it doesn't require re-implementing parse or creating a new format string)
- Custom format specifier with {fmt} for custom class (Discards formatting or requires manual re-implementation of formatting)
- The tutorial I mostly copied (Almost what I want, but still creating a new format string instead of reusing the one originally specified by the user)
- The Docs (Again, either suggests re-implementing the parse function and recalling
formatwith a new format string, despite the caller having already specified one and there being an existing parse function for the type...)
You can get rid of your
parseimplementation and use the inherited function, and usefmt::formatter<ValueType>::format(item, context)inside yourformatto output each item (godbolt demo):You can see other examples of this pattern in the docs under Formatting User-defined Types.