C++23中遍历无继承关系的异构对象并调用模板函数的实现方案
C++23中遍历无继承关系的异构对象并调用模板函数的实现方案
嗨,这个问题我之前在项目里也碰到过,异构类型的遍历确实是C里的一个小麻烦,不过好在C23给了我们不少顺手的工具,完全不用靠继承或者void*这种不太优雅的方式来解决。
核心思路其实很简单:既然这些变量没有继承关系,那我们就用类型安全的异构容器来把每个变量和它的名称绑定起来,然后遍历这个容器,让编译器自动为每个类型实例化模板函数。下面给你具体的实现方案:
方法一:用std::tuple+折叠表达式实现编译期遍历
首先,我们可以把每个需要处理的变量和它对应的JSON键名打包成一个条目,然后把所有条目放进std::tuple里(tuple天生就是为存储异构类型设计的)。之后用std::apply配合折叠表达式展开tuple,对每个条目调用ReadJSONkey。
具体代码示例:
#include <tuple> #include <string> #include <map> // 假设已经包含ArduinoJson的头文件和ReadJSONkey的模板定义 // 先定义你的全局/成员变量 int Pins; unsigned long SerialSpeed; std::string NamePrefix; std::map<std::string, uint8_t> TimeRules; int LoadParametersFromJSON(JsonDocument& JsonDoc) { int err = 0; // 把每个变量和对应的键名绑定,存入tuple auto param_entries = std::make_tuple( // 用pair打包变量引用和键名,简洁直接 std::pair{std::ref(Pins), "Pins"}, std::pair{std::ref(SerialSpeed), "SerialSpeed"}, std::pair{std::ref(NamePrefix), "NamePrefix"}, std::pair{std::ref(TimeRules), "TimeRules"} ); // 用std::apply展开tuple,折叠表达式逐个处理条目 std::apply([&](auto&&... entries) { // 折叠表达式会为每个条目实例化对应的ReadJSONkey模板 ((err += ReadJSONkey(entries.first.get(), JsonDoc, entries.second)), ...); }, param_entries); return err; }
这里用std::ref是为了在tuple里保存变量的引用(而不是拷贝),这样ReadJSONkey才能直接修改原变量的值。展开tuple的时候,编译器会自动为每个不同的类型生成对应的ReadJSONkey特化,完全类型安全。
方法二:用自定义结构体+constexpr for循环(更易读)
如果觉得pair的可读性不够好,我们可以自定义一个结构体来封装参数和名称,再配合C++20起支持的constexpr for循环遍历tuple,代码会更清晰:
#include <tuple> #include <string> #include <map> // 自定义结构体,封装参数引用和键名,语义更清晰 template <typename T> struct JSONParam { T& param; const char* key_name; }; // 你的变量定义 int Pins; unsigned long SerialSpeed; std::string NamePrefix; std::map<std::string, uint8_t> TimeRules; int LoadParametersFromJSON(JsonDocument& JsonDoc) { int err = 0; // 打包所有参数条目到tuple auto param_list = std::make_tuple( JSONParam{Pins, "Pins"}, JSONParam{SerialSpeed, "SerialSpeed"}, JSONParam{NamePrefix, "NamePrefix"}, JSONParam{TimeRules, "TimeRules"} ); // 用constexpr for循环遍历tuple的每个索引 constexpr auto param_count = std::tuple_size_v<decltype(param_list)>; for (std::size_t i = 0; i < param_count; ++i) { std::apply([&](auto&& entry) { err += ReadJSONkey(entry.param, JsonDoc, entry.key_name); }, std::get<i>(param_list)); } return err; }
这种方式的可读性更好,一眼就能看懂每个条目的含义,而且同样是完全类型安全的,编译器会在编译期检查每个参数的类型是否和ReadJSONkey兼容。
为什么这种方案可行?
- 完全类型安全:每个参数的类型信息都被tuple和模板结构体保存了,编译器会为每个类型实例化对应的
ReadJSONkey,不会有void*那种类型丢失的问题 - 易于维护:新增/删除参数只需要修改tuple里的条目,不需要改动循环逻辑
- 符合C++23的现代风格:充分利用了tuple、折叠表达式、constexpr for等特性,代码简洁高效
这种方案完美解决了“遍历无继承关系的异构对象并保留类型信息”的问题,gcc14完全支持所有用到的特性。
内容来源于stack exchange
相关产品推荐
相关产品推荐

