Lagrange
Loading...
Searching...
No Matches
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 #include <format>
45 #include <type_traits>
46
47template <typename T>
48struct std::formatter<T, std::enable_if_t<std::is_base_of_v<Eigen::DenseBase<T>, T>, char>>
49{
50 std::formatter<typename T::Scalar, char> m_scalar;
51
52 constexpr auto parse(std::format_parse_context& ctx) { return m_scalar.parse(ctx); }
53
54 auto format(T const& a, std::format_context& ctx) const
55 {
56 auto out = ctx.out();
57 for (Eigen::Index ir = 0; ir < a.rows(); ir++) {
58 for (Eigen::Index ic = 0; ic < a.cols(); ic++) {
59 out = m_scalar.format(a(ir, ic), ctx);
60 *out++ = ' ';
61 }
62 if (ir + 1 < a.rows()) {
63 *out++ = '\n';
64 }
65 }
66 return out;
67 }
68};
69
70#else
71
72 // spdlog with fmt (either bundled or external, doesn't matter at this point)
73 #if FMT_VERSION >= 100200
74
75 // Use the new nested formatter with fmt >= 10.2.0.
76 // This support nested Eigen types as well as padding/format specifiers.
77 #include <type_traits>
78
79template <typename T>
80struct fmt::formatter<T, std::enable_if_t<std::is_base_of<Eigen::DenseBase<T>, T>::value, char>>
81 : fmt::nested_formatter<typename T::Scalar>
82{
83 auto format(T const& a, format_context& ctx) const
84 {
85 return this->write_padded(ctx, [&](auto out) {
86 for (Eigen::Index ir = 0; ir < a.rows(); ir++) {
87 for (Eigen::Index ic = 0; ic < a.cols(); ic++) {
88 out = fmt::format_to(out, "{} ", this->nested(a(ir, ic)));
89 }
90 if (ir + 1 < a.rows()) {
91 out = fmt::format_to(out, "\n");
92 }
93 }
94 return out;
95 });
96 }
97};
98
99template <typename Derived>
100struct fmt::is_range<
101 Derived,
102 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
103 : std::false_type
104{
105};
106
107 #elif (FMT_VERSION >= 100000) || (FMT_VERSION >= 90000 && !defined(FMT_DEPRECATED_OSTREAM)) || \
108 (defined(LAGRANGE_FMT_EIGEN_FIX) && defined(_MSC_VER))
109
110 // fmt >= 10.x or fmt 9.x without deprecated ostream support.
111 //
112 // We also uses a fmt::formatter<> with fmt v9.x and certain versions of MSVC to workaround
113 // a compiler bug, triggered by code like this:
114 // ```
115 // Eigen::Matrix3f m;
116 // logger().info("{}", m); // C1001: internal compiler error
117 // ```
118 // This manifests as a C1001 internal compiler error, make it impossible to
119 // debug.
120 //
121 // MSVC bug report: https://developercommunity.visualstudio.com/t/10376323
122 // Version affected: 17.6, 17.7
123 // Bug is fixed in version 17.8
124 //
125 #include <type_traits>
126
127template <typename Derived>
128struct fmt::formatter<
129 Derived,
130 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
131{
132 template <typename ParseContext>
133 constexpr auto parse(ParseContext& ctx)
134 {
135 return m_underlying.parse(ctx);
136 }
137
138 template <typename FormatContext>
139 auto format(const Derived& mat, FormatContext& ctx) const
140 {
141 auto out = ctx.out();
142
143 for (Eigen::Index row = 0; row < mat.rows(); ++row) {
144 for (Eigen::Index col = 0; col < mat.cols(); ++col) {
145 out = fmt::format_to(out, " ");
146 out = m_underlying.format(mat.coeff(row, col), ctx);
147 }
148
149 if (row < mat.rows() - 1) {
150 out = fmt::format_to(out, "\n");
151 }
152 }
153
154 return out;
155 }
156
157private:
158 fmt::formatter<typename Derived::Scalar, char> m_underlying;
159};
160
161template <typename Derived>
162struct fmt::is_range<
163 Derived,
164 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
165 : std::false_type
166{
167};
168
169 #else
170
171 // Include legacy ostr support
172
173 // clang-format off
174 #include <lagrange/utils/warnoff.h>
175 #include <spdlog/fmt/ostr.h>
176 #include <lagrange/utils/warnon.h>
177// clang-format on
178
179template <typename Derived>
180struct fmt::is_range<
181 Derived,
182 std::enable_if_t<std::is_base_of<Eigen::DenseBase<Derived>, Derived>::value, char>>
183 : std::false_type
184{
185};
186
187 #endif
188
189#endif