Search code examples
c++formattingc++20fmt

How to use std::format to format all derived class of the same base class?


I have lots of classes derived from the same base class, and I'm trying to avoid writing a formatter for all derived classes. I tried to only implement the std::formatter for the base class, but passing a derived class object/reference to std::format will trigger compile errors.

C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\format(1496): error C2665: 'std::_Format_arg_traits<_Context>::_Phony_basic_format_arg_constructor': none of the 5 overloads could convert all the argument types with [ _Context=std::format_context ] ...

Minimal code is as follows:

#include <format>
#include <iostream>
#include <string>

using namespace std;

struct Base
{
    virtual string ToString()
    {
        return "Base";
    }
};

struct D1 : public Base
{
    string ToString() override
    {
        return "D1";
    }
};

template <typename CharT> struct ::std::formatter<Base, CharT> : ::std::formatter<::std::string>
{
    // parse is inherited from formatter<string>.
    template <typename FormatContext> auto format(Base &e, FormatContext &ctx) const
    {
        ::std::string name = e.ToString();
        return ::std::formatter<::std::string>::format(name, ctx);
    }
};

int main(int argc, char const *argv[])
{
    string s;

    D1 d;
    s = format("{}", d); // this triggers compile errors saying no overloads found
    cout << s << endl;

    Base &b = d;
    s = format("{}", b); // ok after explicit converting to base reference
    cout << s << endl;

    return 0;
}

I'm assuming that the compiler should automatically convert Derived& to Base&, but that doesn't happen. What's the correct way to achieve this?


Solution

  • Base and D1 are different types. A more appropriate way should be to use constrained templates

    #include <concepts>
    #include <format>
    
    template<std::derived_from<Base> Derived, typename CharT>
    struct std::formatter<Derived, CharT> : std::formatter<std::string> {
      template<typename FormatContext>
      auto format(Derived& e, FormatContext& ctx) const {
        std::string name = e.ToString();
        return std::formatter<std::string>::format(name, ctx);
      }
    };
    

    Demo