如何让std::format具备printf对nullptr的容错处理能力?
将产品中的追踪代码从vsprintf改为std::format后,遇到一个问题:原vsprintf处理char*为nullptr时会输出"null",但std::format会抛出std::fmt_exception。
虽然可以在Trace()函数中捕获异常,但这依赖于开发者主动注意到指针可能为空的情况,我希望能直接在格式化时输出空值状态(比如"(null)")。
尝试通过重写格式说明符实现,但仅在对应特化不存在时有效。示例代码如下:
import std; #include <stdio.h> namespace std { // 为const char*添加std::format特化以自动处理空指针 template<> struct formatter<char const*, wchar_t> : formatter<wstring, wchar_t> { auto format(char const* str, wformat_context& ctx) const { std::string strN(str ? str : "(null)"); std::wstring strW(strN.begin(), strN.end()); return formatter<wstring, wchar_t>::format(strW, ctx); } }; } // namespace std int main() { char const* bob = nullptr; printf("Bob is %s", bob); // 输出 Bob is (null) std::wcout << std::format(L"Bob is: {}", bob) << std::endl; // 输出 Bob is (null) std::cout << std::format("Bob is: {}", bob) << std::endl; // 抛出异常,因为已有对应的formatter特化无法重写 return 0; }
请问有没有办法让第三个案例也输出"Bob is (null)"?
直接特化std::formatter<const char*, char>不符合C++标准(标准库已有该特化,用户不能随意重写std命名空间内的已有特化),可以用以下几种合法方式实现需求:
方法1:自定义包装函数/适配器
写一个简单的包装函数,将const char*转换为安全的字符串视图,空指针时返回"(null)":
#include <format> #include <string_view> #include <iostream> constexpr std::string_view safe_cstr(const char* ptr) { return ptr ? std::string_view(ptr) : "(null)"; } int main() { const char* bob = nullptr; std::cout << std::format("Bob is: {}", safe_cstr(bob)) << std::endl; // 输出:Bob is: (null) }
如果追踪系统是统一调用的,可以在Trace()函数内部自动对所有const char*参数应用这个包装,不需要开发者手动调用。
方法2:自定义格式化类型
创建一个自定义类型来包装const char*,然后为该类型特化std::formatter:
#include <format> #include <iostream> #include <string> struct SafeCStr { const char* ptr; explicit SafeCStr(const char* p) : ptr(p) {} }; template<> struct std::formatter<SafeCStr> : std::formatter<std::string_view> { auto format(SafeCStr sc, format_context& ctx) const { return formatter<std::string_view>::format(sc.ptr ? std::string_view(sc.ptr) : "(null)", ctx); } }; // 可选:添加转换函数,减少代码修改量 // 注意:隐式转换可能带来意外影响,需谨慎使用 inline SafeCStr to_safe(const char* ptr) { return SafeCStr(ptr); } int main() { const char* bob = nullptr; std::cout << std::format("Bob is: {}", to_safe(bob)) << std::endl; // 输出:Bob is: (null) }
这种方式更灵活,还可以扩展处理其他指针类型(比如const wchar_t*)。
方法3:使用格式化扩展库(如fmtlib)
如果项目允许使用第三方库,fmtlib(std::format的原型库)支持通过自定义格式化规则处理空指针,且不需要修改std命名空间:
#include <fmt/core.h> #include <iostream> template <> struct fmt::formatter<const char*> : fmt::formatter<std::string_view> { auto format(const char* ptr, format_context& ctx) const { return formatter<std::string_view>::format(ptr ? std::string_view(ptr) : "(null)", ctx); } }; int main() { const char* bob = nullptr; std::cout << fmt::format("Bob is: {}", bob) << std::endl; // 输出:Bob is: (null) }
不过如果已经使用标准库的std::format,这种方式需要替换为fmtlib调用,适合还未大规模切换的场景。
内容的提问来源于stack exchange,提问作者Benj

