Lagrange
fmt_eigen.h
1/*
2 * Copyright 2023 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13#pragma once
14
15// Dealing with fmt-Eigen shenanigans. Ostream support was deprecated in fmt v9.x, and removed from
16// the library in fmt v10.x [1]. Supposedly this was causing ODR violations [2] and was a source of
17// headaches. The consequence of this is that formatting Eigen objects is broken with fmt >= v9.x.
18// Neither the fmt author [3] nor the Eigen maintainers are really interested in shipping &
19// supporting a fmt::formatter<> for Eigen objects. We provide one here as a workaround until a
20// better solution comes along. We also provide an option to override this header with your own
21// hook in case this conflicts with another fmt::formatter<> somewhere else in your codebase.
22//
23// [1]: https://github.com/fmtlib/fmt/issues/3318
24// [2]: https://github.com/fmtlib/fmt/issues/2357
25// [3]: https://github.com/fmtlib/fmt/issues/3465
26
27// clang-format off
28#include <lagrange/utils/warnoff.h>
29#include <spdlog/spdlog.h>
30#include <spdlog/fmt/fmt.h>
31#include <spdlog/fmt/ranges.h>
32#include <lagrange/utils/warnon.h>
33// clang-format on
34
35#include <Eigen/Core>
36
37#ifdef LA_FMT_EIGEN_FORMATTER
38
39 // User-provided fmt::formatter<> for Eigen types.
40 #include LA_FMT_EIGEN_FORMATTER
41
42#elif defined(SPDLOG_USE_STD_FORMAT)
43
44// It's still a bit early for C++20 format support...
45
46#else
47
48 // spdlog with fmt (either bundled or external, doesn't matter at this point)
49 #if FMT_VERSION >= 100200
50
51 // Use the new nested formatter with fmt >= 10.2.0.
52 // This support nested Eigen types as well as padding/format specifiers.
53 #include <type_traits>
54
55template <typename T>
56struct fmt::formatter<T, std::enable_if_t<std::is_base_of<Eigen::DenseBase<T>, T>::value, char>>
57 : fmt::nested_formatter<typename T::Scalar>
58{
59 auto format(T const& a, format_context& ctx) const
60 {
61 return this->write_padded(ctx, [&](auto out) {
62 for (Eigen::Index ir = 0; ir < a.rows(); ir++) {
63 for (Eigen::Index ic = 0; ic < a.cols(); ic++) {
64 out = fmt::format_to(out, "{} ", this->nested(a(ir, ic)));
65 }
66 if (ir + 1 < a.rows()) {
67 out = fmt::format_to(out, "\n");
68 }
69 }
70 return out;
71 });
72 }
73};
74
75template <typename Derived>
76struct fmt::is_range<
77 Derived,
78 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
79 : std::false_type
80{
81};
82
83 #elif (FMT_VERSION >= 100000) || (FMT_VERSION >= 90000 && !defined(FMT_DEPRECATED_OSTREAM)) || \
84 (defined(LAGRANGE_FMT_EIGEN_FIX) && defined(_MSC_VER))
85
86 // fmt >= 10.x or fmt 9.x without deprecated ostream support.
87 //
88 // We also uses a fmt::formatter<> with fmt v9.x and certain versions of MSVC to workaround
89 // a compiler bug, triggered by code like this:
90 // ```
91 // Eigen::Matrix3f m;
92 // logger().info("{}", m); // C1001: internal compiler error
93 // ```
94 // This manifests as a C1001 internal compiler error, make it impossible to
95 // debug.
96 //
97 // MSVC bug report: https://developercommunity.visualstudio.com/t/10376323
98 // Version affected: 17.6, 17.7
99 // Bug is fixed in version 17.8
100 //
101 #include <type_traits>
102
103template <typename Derived>
104struct fmt::formatter<
105 Derived,
106 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
107{
108 template <typename ParseContext>
109 constexpr auto parse(ParseContext& ctx)
110 {
111 return m_underlying.parse(ctx);
112 }
113
114 template <typename FormatContext>
115 auto format(const Derived& mat, FormatContext& ctx) const
116 {
117 auto out = ctx.out();
118
119 for (Eigen::Index row = 0; row < mat.rows(); ++row) {
120 for (Eigen::Index col = 0; col < mat.cols(); ++col) {
121 out = fmt::format_to(out, " ");
122 out = m_underlying.format(mat.coeff(row, col), ctx);
123 }
124
125 if (row < mat.rows() - 1) {
126 out = fmt::format_to(out, "\n");
127 }
128 }
129
130 return out;
131 }
132
133private:
134 fmt::formatter<typename Derived::Scalar, char> m_underlying;
135};
136
137template <typename Derived>
138struct fmt::is_range<
139 Derived,
140 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
141 : std::false_type
142{
143};
144
145 #else
146
147 // Include legacy ostr support
148
149 // clang-format off
150 #include <lagrange/utils/warnoff.h>
151 #include <spdlog/fmt/ostr.h>
152 #include <lagrange/utils/warnon.h>
153// clang-format on
154
155template <typename Derived>
156struct fmt::is_range<
157 Derived,
158 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
159 : std::false_type
160{
161};
162
163 #endif
164
165#endif