Fmt: using custom formatter for types with stream operator

Created on 30 Nov 2018  路  7Comments  路  Source: fmtlib/fmt

Hi,

I am trying to let fmt::format("{}", m) to make use of the custom formatter as described in the API doc by defining specialization of the template class fmt::formatter with m's type M.

However, M is from a third party library (to be specific, in my case M is Eigen::EigenBase<Derived>) that has its operator << defined.

Not including fmt/ostream.h is not an option for me because I do want the types that doesn't comes with a fmt::formatter specialization fall back to use its operator <<.

But, if I include fmt/ostream.h, it will simply shadow my partial specialization of fmt::formatter<M, Char>, and calling fmt::format("{}", m) will just be os << m.

The current solution I came up with is to define a wrapper class pprint<M>(M m) so that I can do fmt::format("{}", pprint(m)), but this looks cumbersome.

Could you please help on this?

Most helpful comment

formatter specializations now always take precedence over operator<<, so the following just works without the need for any workarounds:

#include <iostream>
#include <fmt/ostream.h>

template <typename T>
struct S {};

template <typename T>
std::ostream& operator<<(std::ostream& os, S<T>) {
  return os << 1;
}

template <typename T>
struct fmt::formatter<S<T>> : fmt::formatter<int> {
  auto format(S<T>, format_context& ctx) {
    return formatter<int>::format(2, ctx);
  }
};

int main() {
  std::cout << S<int>() << "\n"; // prints 1 using operator<<
  fmt::print("{}\n", S<int>());  // prints 2 using formatter
}

All 7 comments

I feel your pain. This was one of the topics of my talk at Meeting C++ 2018.

Given it's current architecture of custom formatters, afaik there is no way to make user-supplied custom formatters more specialized than the one automatically generated by fmt/ostream.h. The architecture of the fmt::formatter template just doesn't offer a template argument slot to disregard the automatically generated one as generally less specialized. The only - horrible 馃槺 - option that I found so far is to #include fmt/ostream.h after the definition of the user-supplied specialization of fmt::formatter and metaprogram the automatically generated one away in this case. Afaics this brittle (to say the least) option is not worth the effort to make this work.

I'd love to see a better way to work around this migration roadblock.

If you don't care about multiple character type support and can fully specialize formatter you can make it work:

#include <iostream>
#include <fmt/ostream.h>

struct S {};

std::ostream& operator<<(std::ostream& os, S) {
  return os << 1;
}

template <>
struct fmt::formatter<S> : fmt::formatter<int> {
  template <typename FormatContext>
  auto format(S, FormatContext& ctx) {
    return formatter<int>::format(2, ctx);
  }
};

int main() {
  std::cout << S() << "\n"; // prints 1
  fmt::print("{}\n", S());  // prints 2
}

It should be possible to make it work with partial specializations too by not having ostream support plug into the general extension API, but using a separate lower-priority extension point. This will require some changes to {fmt}.

I would suggest adding a simple trait enable_ostream_fallback<T> that defaults to std::true_type. The ostream specialization is then only enabled if that trait is true, so can be specialized to std::false_type for types that shouldn't use the fallback.

There is already such trait called internal::is_streamable. @Jerry-Ma, will it work for you if we make this trait public?

Thank you all for the insights. So basically there is no easy (at least not difficult) way to achieve this. I guess I'll just stick to what we have and use the wrapper object.

@vitaut I don't think is_streamable would work for me because what I would like to do is to make an already existing streamable class to act as if it is not streamable, so my custom formatter would be used in format.

@foonathan you option would work if this enable_ostream trait is some magic on the formatter class, which can be set in the user-provided specialization of one user type, then the ostream specialization will check this trait to determine whether to skip specializing the operator << from the user type, resulting in effectively using the user provided specialization. However, I am not a black-belt C++ coder so have no idea whether this is doable.

what I would like to do is to make an already existing streamable class to act as if it is not streamable, so my custom formatter would be used in format.

You can use is_streamable to do this by specializing it to false_type as @foonathan suggested:

#include <iostream>
#include <fmt/ostream.h>

template <typename T>
struct S {};

template <typename T>
std::ostream& operator<<(std::ostream& os, S<T>) {
  return os << 1;
}

template <typename T>
struct fmt::formatter<S<T>> : fmt::formatter<int> {
  template <typename FormatContext>
  auto format(S<T>, FormatContext& ctx) {
    return formatter<int>::format(2, ctx);
  }
};

template <typename T>
struct fmt::internal::is_streamable<S<T>, char>
  : std::false_type {};

int main() {
  std::cout << S<int>() << "\n"; // prints 1 using operator<<
  fmt::print("{}\n", S<int>());  // prints 2 using formatter
}

formatter specializations now always take precedence over operator<<, so the following just works without the need for any workarounds:

#include <iostream>
#include <fmt/ostream.h>

template <typename T>
struct S {};

template <typename T>
std::ostream& operator<<(std::ostream& os, S<T>) {
  return os << 1;
}

template <typename T>
struct fmt::formatter<S<T>> : fmt::formatter<int> {
  auto format(S<T>, format_context& ctx) {
    return formatter<int>::format(2, ctx);
  }
};

int main() {
  std::cout << S<int>() << "\n"; // prints 1 using operator<<
  fmt::print("{}\n", S<int>());  // prints 2 using formatter
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dvhwgumby picture dvhwgumby  路  3Comments

shrinktofit picture shrinktofit  路  3Comments

beojan picture beojan  路  5Comments

ugenehan picture ugenehan  路  3Comments

obiphi picture obiphi  路  5Comments