Jul 20, 2024 使用yalantinglibs中的反射库格式化自定义struct/enum 2024-07-20目录std::format的基本用法 .................................................. 1基本用法 .............................................................. 1扩展用法 .............................................................. 1其他用法 .............................................................. 1yalantinglibs的reflection .............................................. 1几个api ............................................................... 1int[]的formatter ...................................................... 1struct的format函数 ................................................. 1enum的format函数 ................................................. 1测试 ................................................................... 1所有代码 ................................................................. 1后 ........................................................................ 1yalantinglibs是一个c++20的库集合。 std::formatter是c++20的新特性,用来格式化内容。这里尝试使用ylt里的反射库实现自定义struct/enum的格式化输出std::format的基本用法基本用法头文件1#include <format>一眼看起来和python rust的格式化语法很类似。实际用起来,受限制很多。比如std::format的控制字符串需要编译期检查,没法运行时生成,也没有python的1print(f"a = {a}")(这叫啥?),参数只能放到后面1std::println("a = {}", a)。 总之,比没有好 。1#include <format>2#include <string>345int main(){6 int a = 42;7 std::string b{"Hello World!"};8 float c = 10.24;9 std::cout << std::format("a = {}, b = {}, c = {:.4f}\n", a, b,c);10 return 0;11}输出内容1a = 42, b = Hello World!, c = 10.24002扩展用法自定义的struct/enum都没有默认支持,需要自定义1std::formatter<T>来实现。具体来说,需要特化1std::formatter<T>,并实现他的两个函数•1parse() 实现如何解析类型的格式字符串说明符•1format() 为自定义类型的对象/值执行实际格式化1#include <iostream>2#include <format>3#include <string>45struct Person {6 std::string name;7 int age;8};910// 定义Person的formatter11template<>12struct std::formatter<Person> {13 static constexpr auto parse(std::format_parse_context& ctx) {14 return ctx.begin();15 }1617 auto format(const Person& p, std::format_context& ctx) const {18 return std::format_to(ctx.out(), "Person(name: {}, age:{})", p.name, p.age);19 }20};2122int main() {23 Person p{"Alice", 30};24 std::string s = std::format("{}", p);25 std::cout << s << std::endl;2627 return 0;28}29输出1Person(name: Alice, age: 30)2其他用法太多了,不给自己挖坑了。推荐看这个c++20 compelte guide•1std::vformat() 和 vformat_to()•格式字符串的语法•标准格式说明符:1fill align sign # 0 width .prec L type•全局化,语言环境•错误处理•自定义格式•自定义格式的解析格式字符串yalantinglibs的reflection官方示例test_reflection文章C++20 非常好用的编译期反射库,划重点【没有宏,没有侵入式】几个api1.1constexpr auto sz = ylt::reflection::members_count<S>();获取S的成员个数。编译期计算。2.1template <typename T, typename Visit> inline constexpr voidfor_each(Visit func)遍历1struct T的每一个成员。Visit func可以是•1[](auto& field) {}•1[](auto &field, auto name, auto index) {}•1[](auto& field, auto name) {}3.1constexpr auto type_name = ylt::reflection::type_string<S>();获取S的类型字符串有这几个就可以写出struct的formatter了。int[]的formatter直接放代码吧> 我遇到的struct都是C的API,所以没有太复杂的类型。1template<int T>2struct std::formatter<int[T]> {3 constexpr auto parse(std::format_parse_context &ctx) {4 return ctx.begin();5 }67 template<typename FormatContext>8 auto format(const int p[T], FormatContext &ctx) const {9 ctx.out() = std::format_to(ctx.out(), "int[{}]{{", T);10 for (int i = 0; i < T; ++i) {11 if (i == T - 1) {12 ctx.out() = std::format_to(ctx.out(), "{}", p[i]);13 } else {14 ctx.out() = std::format_to(ctx.out(), "{}, ",p[i]);15 }16 }17 ctx.out() = std::format_to(ctx.out(), "}}");18 return ctx.out();19 }2021};可以将int[]数组格式化掉。struct的format函数这里把formatter的parse函数单独实现为一个模板函数。1template<typename S, typename FormatContext>2auto my_struct_format(const S &p, FormatContext &ctx) {3 constexpr auto struct_name = ylt::reflection::type_string<S>();4 ctx.out() = std::format_to(ctx.out(), "{}{{ ", struct_name);5 constexpr auto sz = ylt::reflection::members_count<S>();6 ylt::reflection::for_each(p, [&ctx](auto &field, auto name,auto index) {7 if (index < sz - 1) {8 ctx.out() = std::format_to(ctx.out(), "{}: {}, ", name,field);9 } else {10 ctx.out() = std::format_to(ctx.out(), "{}: {} }}",name, field);11 }12 });13 return ctx.out();14}首先获取struct的名字,获取结构体成员个数。再利用1ylt::reflection::for_each遍历每个成员和每个值。特判是否是最后一个成员,处理1,。enum的format函数enum没有出现在官方例子里, 我这瞎搞了半天,看样子是对的 。直接贴一下关键API1get_enum_arr的实现吧1// Enumerate the numbers in a integer sequence to see if they arelegal enum2// value3template <typename E, std::int64_t... Is>4constexpr inline auto get_enum_arr(5 const std::integer_sequence<std::int64_t, Is...> &) {6 constexpr std::size_t N = sizeof...(Is);7 std::array<std::string_view, N> enum_names = {};8 std::array<E, N> enum_values = {};9 std::size_t num = 0;10 (([&]() {11 constexpr auto res = try_get_enum_name<E,static_cast<E>(Is)>();12 if constexpr (res.first) {13 // the Is is a valid enum value14 enum_names[num] = res.second;15 enum_values[num] = static_cast<E>(Is);16 ++num;17 }18 })(),19 ...);20 return std::make_tuple(num, enum_values, enum_names);21}三个返回值:•num: enum的成员个数•enum_values:每个成员的值,是一个数组,大小是可变模板参数Is的多少。•enum_names: 每个成员的名称,是一个数组,大小同上。这个函数要求传入定长的integer sequence,一般情况enum的枚举个数不一定一样。我这里直接传个20,假设我要格式化的enum的枚举个数都不超过20。看上边这个代码,20这个参数用来申请内存空间的,多了无所谓,预留默认空值。这样,enum的格式化函数如下。1template<typename S, typename FormatContext>2auto my_enum_format(const S &p, FormatContext &ctx) {3 constexpr auto index_enum_str =ylt::reflection::get_enum_arr<S>(std::make_integer_sequence<int64_t,20>{});4 constexpr auto enum_name = ylt::reflection::type_string<S>();5 for (int i = 0; i < 20; ++i) {6 if (std::get<1>(index_enum_str)[i] == p) {7 ctx.out() = std::format_to(8 ctx.out(),9 "{}::{}(value = {})",10 // 这个p别忘了强制转为p,不然无限递归了11 enum_name, std::get<2>(index_enum_str)[i],(int) p12 );13 break;14 }15 }16 return ctx.out();17}测试1struct S1 {2 int a;3 float b;4 int c[6];5 float d[3];6};78enum E1 {9 XX = 10,10 YY,11 MAX12};1314int main() {15 S1 s{.a = 42, .b = 10.24, .c = {0, 1, 4, 9, 16, 25}, .d ={1.1, 2.2, 3.3}};16 std::cout << std::format("s = {}\n", s);17 E1 y = E1::MAX;18 std::cout << std::format("y = {}", y);19 return 0;20}输出结果如下1s = S1{ a: 42, b: 10.24, c: int[6]{0, 1, 4, 9, 16, 25}, d: float[3]{1.1, 2.2, 3.3} }2y = E1::MAX(value = 12)所有代码1cmake_minimum_required(VERSION 3.15)2project(test_formatter)34set(CMAKE_CXX_STANDARD 20)567include(FetchContent)89FetchContent_Declare(10 yalantinglibs11 GIT_REPOSITORY https://github.com/alibaba/yalantinglibs.git12 GIT_TAG "0.3.5" # optional ( default master / main )13 GIT_SHALLOW 1 # optional ( --depth=1 )14)1516FetchContent_MakeAvailable(yalantinglibs)171819add_executable(${PROJECT_NAME} main.cpp)20target_link_libraries(${PROJECT_NAME} PRIVATEyalantinglibs::yalantinglibs)21target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)main.cpp pastebin后小坑小点还是挺多的。比如std::formatter的特化没法放到某个namespace里。即1namespace test_space{2 struct S{};3 templace<>4 struct std::formatter<S>{5 // ...6 };7}会编译失败。虽然达到我想要的结果了,但是有几个点感觉不是清晰。1.int[T]的格式化,能再写成个模板好了,可以迭代的容器的格式化。不知道view行不行。2.enum的格式化,感觉应该有个api获取enum的格式,在编译期计算,这样传给get_enum_arr就不会多余或者不够了。应该是我没找到相关内容。3.每个struct都要写一个宏。1MY_STRUCT_FORMAT(S1); 1MY_ENUM_FORMAT(E1);这样。唉,不是不能用。