Is there a way to suppress the fmt range formatter for a user defined class?
Asked Answered
O

1

9

I have a simple class that has a tostring() method:

class MyClass {
  public:

    std::string tostring() const;

    static iterator begin();
    static iterator end();
};

Although I'm using the fmt library now, this code is ported from code that did not, so many of the legacy classes implement a tostring() method, and I have a template that will generate a fmt::formatter for any class that has that method. It's been working fine.

This particular class, however, also has begin/end functions. They're static (this class is similar to an enumeration and you can iterate through all possible values), though, and shouldn't have anything to do with formatting.

Everything was okay until I needed to include fmt/ranges.h for some different code. The problem is that there is a range formatter that sees the begin/end functions and wants to format the class as a range. Now, if I try to format the class, I get an ambiguous instantiation of the formatter (one for the template I want to use and one for the range formatter).

Is there a way to get the range formatter to ignore this class?

A complete example is:

#include <type_traits>
#include <utility>
#include <string>
#include <vector>
#include <fmt/format.h>
// #include <fmt/ranges.h>


// Create formatter for any class that has a tostring() method

template <typename T>
struct has_tostring_member {
private:
  template <typename U>
  static std::true_type  test( decltype(&U::tostring) );
  template <typename U>
  static std::false_type test(...);
public:
  using result = decltype(test<T>(0) );
  static constexpr bool  value = result::value;
};

template <typename T, typename Char>
struct  fmt::formatter<T, Char,
                       std::enable_if_t<has_tostring_member<T>::value > >
  : formatter<basic_string_view<Char>, Char> {
  template <typename FormatContext>
  auto
  format( const T& e, FormatContext& ctx )
  {
    return formatter<string_view>::format( e.tostring(), ctx );
  }
};


class MyClass
{
public:
  explicit MyClass(int i) : value(i) {}

  std::string tostring() const { return std::to_string(value); }

  static auto begin() { return std::begin(static_data); }
  static auto end() { return std::end(static_data); }

private:
  int  value;
  static const std::vector<std::string> static_data;
};


 const std::vector<std::string> MyClass::static_data{ "a", "b", "c" };

int main(void) {
  MyClass c{10};

  fmt::print("c is {}\n", c);

  return 0;
}

If I have a full specialization of fmt::formatter for MyClass, then there is no ambiguity, but if I use a partial specialization as I do in the example, then uncommenting the "#include <fmt/ranges.h>" will cause an ambiguous template instantiation.

Olivas answered 21/4, 2021 at 14:13 Comment(1)
In other words, create minimal reproducible exampleLevitt
O
4

Here are the options (which you've already discovered):

  1. Don't include fmt/ranges.h.
  2. Provide a full specialization of formatter.

Note that if you have implemented a generic formatter for all types that have tostring you can do (2) in just one line (https://godbolt.org/z/cW3WzaP6f):

template <> struct fmt::formatter<MyClass> : tostring_formatter<MyClass> {};
Otway answered 22/4, 2021 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.