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

Consume multiple arguments once in a custom formatter #1700

Closed
Krantz-XRF opened this issue May 26, 2020 · 1 comment
Closed

Consume multiple arguments once in a custom formatter #1700

Krantz-XRF opened this issue May 26, 2020 · 1 comment

Comments

@Krantz-XRF
Copy link

I want to implement a ternary-operator-like formatter:

fmt::format("{:?{}:{}}", true, "Hello", 42) == "Hello";

I have read about implementing custom formatters by specializing fmt::formatter (or inheriting fmt::arg_formatter), but the format function (or resp. operator()) accepts only one argument. I noticed that the basic formatter for integers allows the format string like {:{}.{}}, and consumes 3 arguments, so I wonder whether custom formatters can also do similar things.

I don't want to wrap the 3 arguments into a new structure, for I might want to reuse those arguments in the format string elsewhere, like:

fmt::format("{cond:?{a}:{b}}, {a}", "cond"_a=true, "a"_a="Hello", "b"_a=42);

Ideally, I want to support full-featured format strings in the two branches and allow omitting one branch in that ternary operator:

fmt::format("{:?{} and {:{}.{}}:}", true, "Hello", 4.2, 3, 2);

Note that in the true branch, 2 format specs and some plain text are used, while in the false branch an empty string is used (the whole branch is omitted).

@vitaut
Copy link
Contributor

vitaut commented May 26, 2020

You can get argument IDs in the parse function of your formatter via the parse context: https://fmt.dev/latest/api.html#_CPPv2N3fmt26basic_format_parse_contextE and then retrieve the arguments themselves in the format function via format_context::get. Here's an example copied from #1636 (comment):

#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.

You'll need a separate type to specialize formatter, overriding built-in and string formatters is not allowed.

@vitaut vitaut closed this as completed May 26, 2020
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