Search code examples
c++formattingprotocol-buffersfmt

Provide custom fmt::formatter for protobuf message types


I have some protobuf message classes that I would like to be able to print using fmtlib. For example, I have a TaskStatus message where TaskStatus is derived from google::protobuf::Message. I am using the following code to do this:

template <> struct fmt::formatter<google::protobuf::Message> : formatter<std::string>
{
    // parse is inherited from formatter<string>

    auto format(const google::protobuf::Message& message, format_context& ctx) const -> format_context::iterator
    {
        const auto result = fmt::format("{}", message.DebugString());
        return formatter<std::string>::format(result, ctx);
    }
};

But the compiler seems not to consider that code. If I try to compile code like this:

void TaskServer::UpdateStatus(const TaskStatus& status)
{
    fmt::print("TaskServer::UpdateStatus(): {}", status) // this is line 421
}

then I get these error messages:

ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h: In instantiation of ?constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_value(T&&) [with Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; T = const TaskStatus&]?:
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:1777:29:   required from ?constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_arg(T&&) [with bool IS_PACKED = true; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; type <anonymous> = fmt::v9::detail::type::custom_type; T = const TaskStatus&; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]?
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:1901:77:   required from ?constexpr fmt::v9::format_arg_store<Context, Args>::format_arg_store(T&& ...) [with T = {const TaskStatus&}; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {TaskStatus}]?
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:1918:31:   required from ?constexpr fmt::v9::format_arg_store<Context, typename std::remove_cv<typename std::remove_reference<Args>::type>::type ...> fmt::v9::make_format_args(Args&& ...) [with Context = basic_format_context<appender, char>; Args = {const TaskStatus&}]?
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:3294:44:   required from ?void fmt::v9::print(format_string<T ...>, T&& ...) [with T = {const TaskStatus&}; format_string<T ...> = basic_format_string<char, const TaskStatus&>]?
PsDemo/PsDemo_Diagnostics/TaskServer.cpp:421:19:   required from here
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:1757:7: error: static assertion failed: Cannot format an argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#udt
   1757 |       formattable,
        |       ^~~~~~~~~~~
ThirdParty/spdlog/include/spdlog/fmt/bundled/core.h:1757:7: note: ?formattable? evaluates to false

If I change the fmt::formatter template to use TaskStatus instead of google::protobuf::Message then it works fine.

template <> struct fmt::formatter<TaskStatus> : formatter<std::string>
{
    auto format(const TaskStatus& message, format_context& ctx) const -> format_context::iterator

But I have lots of different classes derived from google::protobuf::Message that I would like to handle with the one function. Is there a way to get the format function using the google::protobuf::Message argument to work for all derived classes ?


Solution

  • fmt::formatter is specialized for the class Message in the namespace google::protobuf. Whereas TaskStatus is not in the namespace google::protobuf. You should specialize fmt::formatter for TaskStatus.

    template <>
    struct fmt::formatter<TaskStatus> : formatter<std::string> {
      auto format(const TaskStatus& message, format_context& ctx) const {
        auto result = fmt::format("{}", message.DebugString());
        return formatter<std::string>::format(result, ctx);
      }
    };
    

    Live example https://godbolt.org/z/GsjzM8b5G

    Specialization for the base class with C++20 concepts:

    template <typename T>
      requires std::is_base_of_v<google::protobuf::Message, T>
    struct fmt::formatter<T> : formatter<std::string> {
      auto format(const T& message, format_context& ctx) const {
        auto result = fmt::format("{}", message.DebugString());
        return formatter<std::string>::format(result, ctx);
      }
    };
    

    Live example https://godbolt.org/z/M8186nzor