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

using custom formatter for types with stream operator #952

Closed
Jerry-Ma opened this issue Nov 30, 2018 · 7 comments
Closed

using custom formatter for types with stream operator #952

Jerry-Ma opened this issue Nov 30, 2018 · 7 comments

Comments

@Jerry-Ma
Copy link

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?

@DanielaE
Copy link
Contributor

DanielaE commented Dec 1, 2018

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.

@vitaut
Copy link
Contributor

vitaut commented Dec 2, 2018

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

@foonathan
Copy link
Contributor

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.

@vitaut
Copy link
Contributor

vitaut commented Dec 3, 2018

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

@Jerry-Ma
Copy link
Author

Jerry-Ma commented Dec 3, 2018

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.

@vitaut
Copy link
Contributor

vitaut commented Dec 3, 2018

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
}

@vitaut
Copy link
Contributor

vitaut commented Jan 21, 2019

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
}

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

4 participants