Search code examples
eigeneigen3fmt

eigen3 with libfmt >= 9.0


I used to be able to pass Eigen3 arrays/matrices to spdlog, which internally uses libfmt. Starting with libfmt 9.0.0, these types are no longer formatted by libfmt without further code.

Custom types are supported in fmt by specializing fmt::formatter<T> for the type; fmt9 documentation further explains that deriving this specialization from ostream_formatter will make use of existing operator<<, which Eigen classes conveniently provide.

Eigen uses the CRTP inheritance and I will specialize for all Eigen::DenseBase<T> types like this:

#include<fmt/ostream.h>
#include<eigen3/Eigen/Core>
#include<iostream>

template <typename T> struct fmt::formatter<T,std::enable_if_t<std::is_base_of_v<Eigen::DenseBase<T>,T>>>: ostream_formatter {};

int main(){
    Eigen::Array3f a(1,2,3);
    std::cout<<fmt::format("{}",a)<<std::endl;
}

Since I am posting, it is already clear this did not go well:

In file included from /usr/include/fmt/format.h:48,
                 from /usr/include/fmt/ostream.h:18,
                 from /tmp/aa.cpp:1:
/usr/include/fmt/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 = Eigen::Array<float, 3, 1>&]’:
/usr/include/fmt/core.h:1771: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>; fmt::v9::detail::type <anonymous> = fmt::v9::detail::type::custom_type; T = Eigen::Array<float, 3, 1>&; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]’
/usr/include/fmt/core.h:1895:77:   required from ‘constexpr fmt::v9::format_arg_store<Context, Args>::format_arg_store(T&& ...) [with T = {Eigen::Array<float, 3, 1, 0, 3, 1>&}; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {Eigen::Array<float, 3, 1, 0, 3, 1>}]’
/usr/include/fmt/core.h:1912: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 = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {Eigen::Array<float, 3, 1, 0, 3, 1>&}]’
/usr/include/fmt/core.h:3184:44:   required from ‘std::string fmt::v9::format(fmt::v9::format_string<T ...>, T&& ...) [with T = {Eigen::Array<float, 3, 1, 0, 3, 1>&}; std::string = std::__cxx11::basic_string<char>; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, Eigen::Array<float, 3, 1, 0, 3, 1>&>]’
/tmp/aa.cpp:7:24:   required from here
/usr/include/fmt/core.h:1751: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
 1751 |       formattable,
      |       ^~~~~~~~~~~
/usr/include/fmt/core.h:1751:7: note: ‘formattable’ evaluates to false

Can I get some explanation how to do this correctly? I would like to both understand what's going c++-wise here and also have a working solution based on that. Thanks!


Solution

  • The problem is that formatter takes a character (code unit) type as a second template parameter and you pass void there via enable_if_t which cannot possibly work. The fix is to pass char or another code unit type:

    template <typename T>
    struct fmt::formatter<
      T,
      std::enable_if_t<
        std::is_base_of_v<Eigen::DenseBase<T>, T>,
        char>> : ostream_formatter {};
    //  ^ code unit type
    

    Godbolt: https://godbolt.org/z/x1Tr3MPYY