Search code examples
c++polymorphismoverloadingderived-classfmt

Custom class fmt::formatter for non const argument


I have a class:

class MyClass{
   public:
   std::string _cachedString;
   std::string_view get_string(){
      _cachedString = "abc";
      return _cachedString;
   }
};

template<>
class fmt::formatter<MyClass>{
    public:
    template<typename ParseContext>
    constexpr auto parse(ParseContext& ctx){return ctx.begin();}

    template <typename FormatContext>
    constexpr auto format( MyClass& t, FormatContext& ctx){
        return format_to(ctx.out(), "({})", t.get_string());
    }
};

I looked this up, and found that you need to pass const MyClass& t as argument to format function. But in my case, since I am modifying the object, I cannot. When I compile this code, I get this error:

 undefined reference to `fmt::v8::detail::value<fmt::v8::basic_format_context<fmt::v8::appender, char> >::value(fmt::v8::detail::unformattable_const)'

which goes away when I remove the line calling fmt::format on MyClass object. How to rectify this?


Solution

  • You should make the format function itself const:

    template<>
    struct fmt::formatter<MyClass> {
      constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
      }
    
      template <typename FormatContext>
      constexpr auto format(MyClass& t, FormatContext& ctx) const {
        return format_to(ctx.out(), "({})", t.get_string());
      }
    };
    

    Otherwise your example works in recent versions of {fmt} (godbolt):

    int main() {
      MyClass c;
      fmt::print("{}", c);
    }
    

    However, as @super correctly pointed out in the comments, a better solution is to make _cachedString mutable and get_string const:

    class MyClass {
     public:
      mutable std::string _cachedString;
    
      std::string_view get_string() const {
        _cachedString = "abc";
        return _cachedString;
      }
    };