Reputation: 31
I'm trying to define a reusable formatter for a class, let's say cv::Point_ from OpenCV. The code compiles fine on its own but when I use this formatter I get compilation errors. Here is the code:
template<typename T>
struct fmt::formatter<cv::Point_<T>> : fmt::formatter<std::string_view> {
template<typename FormatContext>
auto format(const cv::Point_<T>& point, FormatContext& ctx) const {
// return format_to(ctx.out(), "Point(x={}, y={})", point.x, point.y);
return fmt::format("Point(x={}, y={})", point.x, point.y, ctx);
}
};
From the errors it's obvious that it can't prove that type T
has formatter/can be formatted. Not sure how convince the fmt library. I've tried to replace T
with something like this to no avail:
template<typename R>
concept Arithmetic = std::is_arithmetic_v<R>;
Is this possible at all, if so how do I define a formatter?
Here is one of the compiler errors regardless of the style of how formatter was defined:
/usr/local/include/fmt/ostream.h: At global scope:
/usr/local/include/fmt/ostream.h:185:8: error: partial specialization of ‘struct fmt::v9::detail::fallback_formatter<T, Char, typename std::enable_if<fmt::v9::detail::is_streamable<T, Char>::value, void>::type>’ after instantiation of ‘struct fmt::v9::detail::fallback_formatter<cv::Point_<float>, char, void>’ [-fpermissive]
185 | struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
or:
/usr/local/include/fmt/core.h:2743:12: error: use of deleted function ‘fmt::v9::detail::fallback_formatter<T, Char, Enable>::fallback_formatter() [with T = cv::Rect_<float>; Char = char; Enable = void]’
2743 | auto f = conditional_t<has_formatter<mapped_type, context>::value,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2744 | formatter<mapped_type, char_type>,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2745 | fallback_formatter<stripped_type, char_type>>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/fmt/core.h:1124:3: note: declared here
1124 | fallback_formatter() = delete;
| ^~~~~~~~~~~~~~~~~~
/usr/local/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 = const cv::Rect_<float>&]’:
/usr/local/include/fmt/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 cv::Rect_<float>&; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]’
/usr/local/include/fmt/core.h:1901:77: required from ‘constexpr fmt::v9::format_arg_store<Context, Args>::format_arg_store(T&& ...) [with T = {const cv::Rect_<float>&, const float&, const int&}; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {cv::Rect_<float>, float, int}]’
/usr/local/include/fmt/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 cv::Rect_<float>&, const float&, const int&}]’
/usr/local/include/fmt/core.h:3206:44: required from ‘std::string fmt::v9::format(format_string<T ...>, T&& ...) [with T = {const cv::Rect_<float>&, const float&, const int&}; std::string = std::__cxx11::basic_string<char>; format_string<T ...> = basic_format_string<char, const cv::Rect_<float>&, const float&, const int&>]’
/usr/local/include/fmt/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,
| ^~~~~~~~~~~
/usr/local/include/fmt/core.h:1757:7: note: ‘formattable’ evaluates to false
Upvotes: 2
Views: 85
Reputation: 71969
From the error messages, it looks like the problem is that your Rect
formatter wants to write out the corners, which are Point
s, but at the point it's doing so, the specialization of the formatter for Point
is not visible to the compiler. This means it uses the generic version (which forwards to fallback_formatter
). Then, when you define the specialization, the compiler complains about doing so after the template already having been used.
This is mostly a code ordering problem.
The other error occurs in the fallback formatter instantiation and should therefore be irrelevant, because that shouldn't happen once you fix the ordering problem.
Upvotes: 0
Reputation: 37697
You are using fmt::format(
instead of fmt::format_to(
to return an iterator for your format funtion.
My attempt:
template<typename T>
struct Point {
T x;
T y;
};
template<typename T>
struct fmt::formatter<Point<T>> : fmt::formatter<T>
{
using Base = fmt::formatter<T>;
using value_type = Point<T>;
auto format(const value_type& p, format_context& ctx) const
-> format_context::iterator
{
fmt::format_to(ctx.out(), "{{");
fmt::format_to(Base::format(p.x, ctx), ", ");
return fmt::format_to(Base::format(p.y, ctx), "}}");
}
};
https://godbolt.org/z/EfsdeMv7f
Note depending on fmt
version there might be small differences in function signature.
Upvotes: 3