如何为模板类定义通用fmt格式化器?以cv::Point_为例
为OpenCV模板类定义fmt格式化器的问题与解决方案
问题描述
尝试为OpenCV的cv::Point_这类模板类定义可复用的fmt格式化器,单独编译代码无问题,但使用时出现编译错误。初始代码如下:
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); } };
编译器提示无法证明类型T具备可格式化能力,尝试用算术概念约束T也无效。
编译错误信息
错误信息1
/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>> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
错误信息2
/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
解决方案
你的代码存在两个核心问题:
- 错误调用格式化接口:在格式化器的
format方法里,不能直接调用fmt::format并传入ctx,正确做法是用fmt::format_to直接写入上下文的输出迭代器,这是自定义格式化器的标准用法。 - 缺少类型约束:需要明确约束
T是fmt可格式化的类型,避免编译器触发无效的fallback formatter逻辑。
正确的格式化器定义
#include <fmt/format.h> #include <opencv2/core/types.hpp> // 为cv::Point_<T>定义格式化器,约束T必须是可格式化类型 template<typename T> requires fmt::formattable<T> struct fmt::formatter<cv::Point_<T>> : fmt::formatter<T> { template<typename FormatContext> auto format(const cv::Point_<T>& point, FormatContext& ctx) const { // 直接写入上下文输出流,复用T的格式化规则 return fmt::format_to(ctx.out(), "Point(x={}, y={})", point.x, point.y); } };
关键说明
- 约束T的可格式化性:通过
requires fmt::formattable<T>确保只有当T本身能被fmt格式化时,该特化才生效,避免编译器尝试错误的回退逻辑。 - 继承fmt::formatter
:相比继承 std::string_view,这样能直接复用T的格式化规则(比如浮点数精度控制),灵活性更高。 - 使用format_to而非format:
format_to直接操作上下文的输出迭代器,符合fmt自定义格式化器的设计规范,不会生成额外临时字符串,效率更高。
扩展到cv::Rect_等其他模板类
同样逻辑可复用在cv::Rect_<T>上:
template<typename T> requires fmt::formattable<T> struct fmt::formatter<cv::Rect_<T>> : fmt::formatter<T> { template<typename FormatContext> auto format(const cv::Rect_<T>& rect, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "Rect(x={}, y={}, width={}, height={})", rect.x, rect.y, rect.width, rect.height); } };
内容的提问来源于stack exchange,提问作者ILikeToLearn
相关产品推荐
相关产品推荐

