如何为自定义to_string模板实现通用fmt::formatter?
复用自定义to_string函数实现fmt通用格式化
需求
已有一组自定义的to_string模板特化,希望让fmt::format自动调用这些函数格式化对应类型,无需为每个类型单独编写fmt::formatter特化。
现有自定义to_string代码:
struct A {}; struct B {}; template <typename T> std::string to_string(const T&); template <> std::string to_string(const A&) { return "A"; } template <> std::string to_string(const B&) { return "B"; }
目标是直接通过fmt::format格式化类型实例:
int main() { A a; B b; std::cout << fmt::format("{} {}\n", a, b); // 期望输出 "A B" }
当前冗余实现
目前可以为每个类型单独编写fmt::formatter特化,但这种方式重复代码多,扩展性差:
template <> struct fmt::formatter<A> { fmt::formatter<fmt::string_view> str_formatter; constexpr auto parse(format_parse_context& ctx) { return str_formatter.parse(ctx); } template <typename FormatContext> auto format(const A& a, FormatContext& ctx) const { return str_formatter.format(::to_string<A>(a), ctx); } }; template <> struct fmt::formatter<B> { fmt::formatter<fmt::string_view> str_formatter; constexpr auto parse(format_parse_context& ctx) { return str_formatter.parse(ctx); } template <typename FormatContext> auto format(const B& b, FormatContext& ctx) const { return str_formatter.format(::to_string<B>(b), ctx); } };
通用化尝试失败
直接全模板特化fmt::formatter<T>会覆盖fmt库原本对所有类型的格式化支持(如int、std::string等),导致编译错误,因此该方案不可行:
// 错误方案:会覆盖fmt原生的formatter特化 template <typename T> struct fmt::formatter<T> { fmt::formatter<fmt::string_view> str_formatter; constexpr auto parse(format_parse_context& ctx) { return str_formatter.parse(ctx); } template <typename FormatContext> auto format(const T& obj, FormatContext& ctx) const { return str_formatter.format(::to_string<T>(obj), ctx); } };
通用化解决方案
利用C++17的SFINAE特性,通过类型检测仅为存在自定义to_string的类型生成fmt::formatter特化,同时复用fmt::formatter<string_view>的解析逻辑:
#include <fmt/core.h> #include <type_traits> // 辅助 trait:检测类型T是否存在有效的::to_string(const T&)重载 template <typename T, typename = void> struct has_custom_to_string : std::false_type {}; template <typename T> struct has_custom_to_string<T, std::void_t<decltype(::to_string(std::declval<const T&>()))>> : std::true_type {}; // 偏特化fmt::formatter,仅对有自定义to_string的类型生效 template <typename T> struct fmt::formatter<T, std::enable_if_t<has_custom_to_string<T>::value>> : fmt::formatter<fmt::string_view> { template <typename FormatContext> auto format(const T& obj, FormatContext& ctx) const { // 调用自定义to_string后,复用string_view的格式化逻辑 return fmt::formatter<fmt::string_view>::format(::to_string(obj), ctx); } };
说明
has_custom_to_stringtrait通过std::void_t和表达式SFINAE检测::to_string(const T&)是否可调用;- 偏特化的
fmt::formatter继承自fmt::formatter<string_view>,自动复用其parse方法,无需重复编写; - 仅对满足
has_custom_to_string<T>::value为true的类型生效,不会干扰fmt原生支持的类型。
内容的提问来源于stack exchange,提问作者abrzozowski
相关产品推荐
相关产品推荐

