C++20下无自定义formatter时std::format输出key的实现方案
需求背景
我在自定义断言宏中使用std::format,希望编写如下代码:
assert( is_found(key) ) << std::format( "The key '{}' was not found", key );
问题在于key的类型由用户提供,我不想强制调用者为该类型实现自定义formatter,但对已有formatter的类型要保留正常格式化功能;当没有可用的formatter时,希望输出:
The key '(no user supplied formatter)' was not found.
有没有现成的实现方案?我也可以接受编写如下代码:
assert( is_found(key) ) << std::format( "The key '{}' was not found", TryToFormat(key) );
其中TryToFormat会在存在formatter时返回原类型的key,不存在时返回MissingT<T>,我会为MissingT<>编写formatter来处理缺失情况。已知C23有std::formattable概念,请问C20中有什么实现方法?
实现方案
在C++20中,我们可以通过SFINAE(替换失败不是错误)和模板重载模拟std::formattable的效果,实现TryToFormat工具,核心是判断类型T是否存在对应的std::formatter<T, char>特化。
1. 检测可格式化类型的Trait
先定义一个模板结构体,用来判断类型是否支持std::format:
#include <format> #include <type_traits> template <typename T, typename = void> struct is_formattable : std::false_type {}; template <typename T> struct is_formattable<T, std::void_t<decltype(std::formatter<T, char>{})>> : std::true_type {}; template <typename T> inline constexpr bool is_formattable_v = is_formattable<T>::value;
这个Trait利用std::void_t检测std::formatter<T, char>能否合法实例化,以此判断类型是否支持格式化。
2. 定义降级包装类及对应Formatter
template <typename T> struct MissingT { const T& value; }; template <typename T> struct std::formatter<MissingT<T>, char> { template <typename FormatContext> auto format(const MissingT<T>&, FormatContext& ctx) const { return std::format_to(ctx.out(), "(no user supplied formatter)"); } };
MissingT仅包装原类型的引用,对应的formatter直接输出降级文本。
3. 实现TryToFormat函数模板
通过SFINAE提供两个重载,分别处理可格式化和不可格式化的情况:
template <typename T> auto TryToFormat(const T& value) -> std::enable_if_t<is_formattable_v<T>, const T&> { return value; } template <typename T> auto TryToFormat(const T& value) -> std::enable_if_t<!is_formattable_v<T>, MissingT<T>> { return {value}; }
当T可格式化时返回原引用;否则返回MissingT<T>对象,后续std::format会自动使用降级格式化逻辑。
4. 使用示例
// 自定义无formatter的类型 struct CustomKey { int id; }; void test() { int int_key = 42; CustomKey custom_key{123}; // 输出:The key '42' was not found assert(false) << std::format("The key '{}' was not found", TryToFormat(int_key)); // 输出:The key '(no user supplied formatter)' was not found assert(false) << std::format("The key '{}' was not found", TryToFormat(custom_key)); }
补充说明
- 该方案完全基于C++20标准特性,无需依赖第三方库。
- 若需支持宽字符(
wchar_t),可将Trait和formatter中的char替换为wchar_t,或进行模板化处理。 - 注意
MissingT保存的是原对象的引用,需确保原对象在格式化时仍有效,避免悬空引用问题。
内容的提问来源于stack exchange,提问作者Steven

