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?
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);
}
};