Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle dynamic format specifiers in custom formatters ? #1636

Closed
BenjaminNavarro opened this issue Apr 18, 2020 · 2 comments
Closed

Comments

@BenjaminNavarro
Copy link

I am currently implementing an fmt-based formatter for the Eigen library since the original one uses ostreams and requires to build a formatter object to change the formatting.

The initial implementation is working and the first tests gives a 4x performance boost, which is great. However I am struggling with one issue and I can't find in the docs how to solve it.

Since my format string can be quite long (lot of parameters) I'd like to be able to construct it once and then pass it as an additional argument to the format function, similar to what is possible for floating point numbers precision (fmt::format("{:.{}f}", 3.14, 1);)

In my case I'd like to do:

constexpr auto heavy_format_str = "p{f};csep{, };rsep{;\n};rpre{[};rsuf{]};mpre{[};msuf{]}";
fmt::print("{:{}}", mat, heavy_format_str);

But I don't understand how to make it works. Do I have to do something specific in my formatter parse function to tell fmt that there is a {} that need to be formatted or do I have to, somehow, access myself the next argument and handle it myself?

@vitaut
Copy link
Contributor

vitaut commented Apr 18, 2020

You need to parse {} in your formatter's parse function, store the argument index and then use it in the format function, for example:

#include <fmt/format.h>

struct foo {};

template <> struct fmt::formatter<foo> {
  int arg_id = -1;

  auto parse(format_parse_context& ctx) {
    auto it = ctx.begin(), end = ctx.end();
    if (it == end || *it == '}') return it;
    if (*it == '{' && ++it != end && *it == '}') {
      arg_id = ctx.next_arg_id();
      return ++it;
    }
    throw format_error("invalid format");
  }

  auto format(const foo& value, format_context& ctx) {
    auto out = ctx.out();
    if (arg_id == -1)
      return format_to(out, "foo"); // default format
    return visit_format_arg([=](auto value) {
        if constexpr (std::is_same_v<decltype(value), const char*>) {
          return format_to(out, "{}", value); // fancy format
        } else {
          throw format_error("expected string argument");
        }
        return out;
      }, ctx.arg(arg_id));
  }
};

int main() {
  fmt::print("{}", foo());
  fmt::print("{:{}}", foo(), "bar");
}

This will print "foobar" with "bar" taken from a dynamically specified string argument.

@vitaut vitaut closed this as completed Apr 18, 2020
@BenjaminNavarro
Copy link
Author

Thanks a lot for your help. I had to stick with C++11 so it was a bit challenging to rewrite your example for that and handle everything properly but I did it. Here is my result if someone is in the same situation as I am and need an example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants