如何使用std::format格式化同一基类的所有派生类对象?
问题原因
std::format 匹配std::formatter特化时,会严格按照传入实参的静态类型做精确匹配,这个匹配过程不会自动触发派生类到基类的隐式引用转换。仅为Base类型编写formatter特化时,传入D1类型对象的场景下,编译器找不到对应D1类型的formatter实现,就会抛出看到的编译错误。只有显式把对象转成Base&类型传入时,参数静态类型变为Base,才能匹配到已有的特化。
正确实现方案
不要写针对Base的全特化,改为编写受约束的偏特化,让它自动匹配所有Base及其公有派生类即可,C++20环境下用std::is_base_of_v配合concept约束就能实现,不需要为每个派生类重复写特化代码。
修正后的可运行代码如下:
#include <format> #include <iostream> #include <string> #include <type_traits> using namespace std; struct Base { virtual string ToString() const { return "Base"; } }; struct D1 : public Base { string ToString() const override { return "D1"; } }; // 约束:仅当T是Base或其公有派生类时,匹配该formatter特化 template <typename T, typename CharT> requires is_base_of_v<Base, T> struct ::std::formatter<T, CharT> : ::std::formatter<::std::string> { // parse方法直接继承string的formatter实现 template <typename FormatContext> auto format(const T& e, FormatContext& ctx) const { ::std::string name = e.ToString(); return ::std::formatter<::std::string>::format(name, ctx); } }; int main(int argc, char const* argv[]) { string s; D1 d; s = format("{}", d); // 正常编译,输出D1 cout << s << endl; Base& b = d; s = format("{}", b); // 正常编译,输出D1 cout << s << endl; return 0; }
实现说明
- 代码中给
ToString方法加了const限定,符合const正确性:格式化接口默认接收const引用的待格式化对象,const对象无法调用非const成员函数,原代码中非const的ToString在接收const派生类对象时会触发额外编译错误。 - 该受约束的偏特化会自动匹配所有
Base的公有派生类,格式化时会通过基类虚函数自动触发多态调用,输出对应派生类ToString方法的返回值。 - 如果使用的编译器不完全支持C++20 concept语法,可替换为SFINAE的
enable_if写法,逻辑完全等价:
template <typename T, typename CharT> struct ::std::formatter<T, CharT, enable_if_t<is_base_of_v<Base, T>, CharT>> : ::std::formatter<::std::string> { // 内部format实现和上述代码完全一致 };
内容的提问来源于stack exchange,提问作者Martin Z He
相关产品推荐
相关产品推荐

