4

I am trying to use spdlog. I incorporated it with my code, but now I am getting the following error:

....fmt\core.h(1016): error C2338: Cannot format argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#formatting-user-defined-types
  ....fmt/core.h(1013): note: while compiling class template member function 'int fmt::v6::internal::arg_mapper<Context>::map(...)'
          with
          [
              Context=context
          ]
  ....fmt/core.h(1213): note: see reference to function template instantiation 'int fmt::v6::internal::arg_mapper<Context>::map(...)' being compiled
          with
          [
              Context=context
          ]
  ....fmt/core.h(1027): note: see reference to class template instantiation 'fmt::v6::internal::arg_mapper<Context>' being compiled
          with
          [
              Context=context
          ]
  ....fmt/core.h(1198): note: see reference to alias template instantiation 'fmt::v6::internal::mapped_type_constant<int,context>' being compiled
  ....fmt/core.h(1342): note: see reference to function template instantiation 'unsigned __int64 fmt::v6::internal::encode_types<Context,int,>(void)' being compiled
          with
          [
              Context=context
          ]
  ....fmt/format.h(3375): note: see reference to class template instantiation 'fmt::v6::format_arg_store<context,int>' being compiled
  ....spdlog/details/fmt_helper.h(49): note: see reference to function template instantiation 'std::back_insert_iterator<fmt::v6::internal::buffer<char>> fmt::v6::format_to<char[6],int&,250,char>(fmt::v6::basic_memory_buffer<char,250,std::allocator<char>> &,const S (&),int &)' being compiled
          with
          [
              S=char [6]
          ]

... and this is the end of the error message. It never reaches my code, so I have no idea where to look at. Any ideas what could be the reason for this?

spdlog is version 1.6.1. The last error line is from here:

inline void pad2(int n, memory_buf_t &dest)
{
    if (n >= 0 && n < 100) // 0-99
    {
        dest.push_back(static_cast<char>('0' + n / 10));
        dest.push_back(static_cast<char>('0' + n % 10));
    }
    else // unlikely, but just in case, let fmt deal with it
    {
        fmt::format_to(dest, "{:02}", n);  // <--- HERE
    }
}

and it doesn't look particularly wrong to me.


Update: after some try-and-errors commenting out all spdlog invocations I narrowed it down to this:

spdlog::info("Foo{}", Point{1, 2});

Where Point is my own class in my own namespace. I do provide for it a way to print itself:

template<typename OStream>
OStream &operator<<(OStream &os, const Point &p) {
    return os << "[" << p.x << ", " << p.y << "]";
}
CygnusX1
  • 20,968
  • 5
  • 65
  • 109
  • Can you provide your `Point` class, please? Did you declare the `operator <<` in it? – arsdever Jun 11 '20 at 08:43
  • Could you please provide some more code where you call the `spdlog::info` – arsdever Jun 11 '20 at 08:46
  • 2
    Add `#include ` – Piotr Skotnicki Jun 11 '20 at 08:47
  • You need the special include @PiotrSkotnicki mentioned if you want your user defined type to be formatted using `operator<<`. Otherwise you need to provide a [fmt::formatter class specialization](https://fmt.dev/latest/api.html#formatting-user-defined-types). – Jorge Bellon Jun 11 '20 at 09:06
  • @JorgeBellon I have a code, that prints user-defined class, but I didn't manually include that file and it works – arsdever Jun 11 '20 at 09:09
  • The documentation [is pretty clear about that](https://fmt.dev/latest/api.html#std-ostream-support). – Jorge Bellon Jun 11 '20 at 09:17
  • Yes, the documentation is clear, and yes that was my omission. Fixed now and works as intended. My only wish is that the error message was a little bit more helpful. @PiotrSkotnicki If you could promote your comment to an answer I will gladly accept it. – CygnusX1 Jun 11 '20 at 13:56

2 Answers2

5

spdlog uses the {fmt} library for formatting the output text, and that requires the <fmt/ostream.h> header file to be included in order to enable std::ostream-like support.

Regardless of using external implementation of the {fmt} library, or the one from spdlog sources itself, there's the special header <spdlog/fmt/ostr.h> that includes the file version that is in use.

Once included, spdlog should be able to use your operator<<.

Alternatively, you can create a custom formatter, that's also capable of parsing a formatting string:

template <>
struct fmt::formatter<Point> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.end();
    }

    template <typename Context>
    auto format(const Point& p, Context& ctx) {
        return format_to(ctx.out(), "[{}, {}]", p.x, p.y);
    }
};
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
3

The other answer is overall correct: spdlog is based on {fmt} library, which has build-in std::ostream support including formatting of user-defined types that have an overloaded insertion operator (operator<<).

But starting from version 9 of {fmt} library, one needs to explicitly specialize fmt::formatter struct for user classes deriving it from ostream_formatter as follows:

#include <spdlog/fmt/ostr.h>

#if FMT_VERSION >= 90000
    template <> struct fmt::formatter<Point> : ostream_formatter{};
#endif

See: https://fmt.dev/dev/api.html#ostream-api

Fedor
  • 17,146
  • 13
  • 40
  • 131