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

1

8

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?

Silurian answered 22/6, 2022 at 5:58 Comment(0)
H
8

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

Housework answered 22/6, 2022 at 6:17 Comment(1)
Perhaps worth mentioning that this solution would not need thatToString() is virtual, but produces a different instantiation for every derived class. If that is not desired and formatting should happen via the virtual function, then this solution should delegate to a formatter that takes the base class so that the compiler can optimize away the instantiations for the derived classes.Emma

© 2022 - 2024 — McMap. All rights reserved.