基于运行时映射构建编译期tuple结构的技术问询
问题描述
我正在开发一项功能,需基于运行时提供的映射创建编译期数据结构。若运行时映射匹配预定义的编译期模式,应生成对应结构;该结构可由对象或对象vector的任意组合构成,所有对象均继承自同一父类,并通过std::variant存储在map中。这本质属于模式匹配场景,若运行时映射不符合编译期定义的模式,允许执行失败,但std::variant大幅增加了实现复杂度。以下是我已完成的代码,希望实现get_tuple函数,自动完成手动构建Test::DataKV_tuple的过程:
#include <vector> #include <unordered_map> #include <string> #include <tuple> #include <variant> #include <stdexcept> template <typename K, typename V> class DataKV{ public: using typeK = K; using typeV = V; }; template <typename... Ts> class DataKVPackage{ public: using DataKVTuple = std::tuple<Ts...>; DataKVTuple data_; explicit DataKVPackage(DataKVTuple& data): data_(data){} }; template <typename T> struct remove_vector { using type = T; }; template <typename T> struct remove_vector<std::vector<T>> { using type = T; }; template <typename T> using remove_vector_t = typename remove_vector<T>::type; template<class Derived, typename... Ts> class Foo_interface{ public: using DataKV_raw_variants = std::variant<remove_vector_t<Ts>...>; using DataKV_tuple = std::tuple<Ts...>; using DataKVPackage = DataKVPackage<Ts...>; Foo_interface()=default; }; template <typename T> class Orchestrator{ public: std::unordered_map<std::string, typename T::DataKV_raw_variants> map_data; void add_data(std::string data_id, const auto& dataKV_ptr){ map_data[data_id] = typename T::DataKV_raw_variants(dataKV_ptr); } }; // 待实现的函数 template <typename T> typename T::DataKV_tuple get_tuple(std::unordered_map<int, std::variant<std::string, std::vector<std::string>>> mapping, Orchestrator<T> orch); // 示例专属定义 using DATA_A = DataKV<int, float>; using DATA_B = DataKV<double, std::string>; using DATA_C = DataKV<char, bool>; class Test : public Foo_interface<Test, std::vector<DATA_A>, DATA_B, std::vector<DATA_C>>{}; // 示例用法 int main() { Orchestrator<Test> foo; DATA_A F_a_0 = DATA_A(); DATA_A F_a_1 = DATA_A(); DATA_A F_a_2 = DATA_A(); DATA_B F_b_0 = DATA_B(); DATA_B F_b_1 = DATA_B(); DATA_B F_b_2 = DATA_B(); DATA_C F_c_0 = DATA_C(); DATA_C F_c_1 = DATA_C(); DATA_C F_c_2 = DATA_C(); foo.add_data("F_a_0", F_a_0); foo.add_data("F_a_1", F_a_1); foo.add_data("F_a_2", F_a_2); foo.add_data("F_b_0", F_b_0); foo.add_data("F_b_1", F_b_1); foo.add_data("F_b_2", F_b_2); foo.add_data("F_c_0", F_c_0); foo.add_data("F_c_1", F_c_1); foo.add_data("F_c_2", F_c_2); // 手动构建方式 std::vector<DATA_A> many_F_a; many_F_a.push_back(F_a_0); many_F_a.push_back(F_a_1); std::vector<DATA_C> many_F_c; many_F_c.push_back(F_c_1); many_F_c.push_back(F_c_2); Test::DataKV_tuple dat_manual = std::make_tuple(many_F_a, F_b_0, many_F_c); Test::DataKVPackage data_pack_manual = DataKVPackage<std::vector<DATA_A>, DATA_B, std::vector<DATA_C>>(dat_manual); // 自动构建的映射配置 std::unordered_map<int, std::variant<std::string, std::vector<std::string>>> mapping; std::vector<std::string> datas_a = {"F_a_0", "F_a_1"}; std::string data_b = "F_b_0"; std::vector<std::string> datas_c = {"F_c_1", "F_c_2"}; mapping[0] = datas_a; mapping[1] = data_b; mapping[2] = datas_c; Test::DataKV_tuple dat_auto = get_tuple<Test>(mapping, foo); // auto data_pack_auto = Test::DataKVPackage(dat_auto); return 0; }
实现方案
以下是get_tuple函数的完整实现,通过编译期类型遍历和运行时数据提取完成自动构建:
// 辅助模板:提取Foo_interface的类型参数列表 template<typename T> struct get_foo_types; template<typename Derived, typename... Ts> struct get_foo_types<Foo_interface<Derived, Ts...>> { using types = std::tuple<Ts...>; }; // 辅助函数:提取单个DataKV实例 template<typename T> T extract_single(const std::string& key, const Orchestrator<auto>& orch) { auto it = orch.map_data.find(key); if (it == orch.map_data.end()) { throw std::runtime_error("Key not found: " + key); } try { return std::get<T>(it->second); } catch (const std::bad_variant_access&) { throw std::runtime_error("Type mismatch for key: " + key); } } // 辅助函数:提取多个DataKV实例并组装成vector template<typename T> std::vector<T> extract_vector(const std::vector<std::string>& keys, const Orchestrator<auto>& orch) { std::vector<T> vec; vec.reserve(keys.size()); for (const auto& key : keys) { vec.push_back(extract_single<T>(key, orch)); } return vec; } // 辅助函数:根据元素类型处理映射项 template<typename ElemType> ElemType process_mapping_item(const std::variant<std::string, std::vector<std::string>>& var, const Orchestrator<auto>& orch) { if constexpr (std::is_same_v<ElemType, remove_vector_t<ElemType>>) { // 当前元素是单个对象 if (!std::holds_alternative<std::string>(var)) { throw std::runtime_error("Expected single string for element type"); } return extract_single<ElemType>(std::get<std::string>(var), orch); } else { // 当前元素是vector using InnerType = remove_vector_t<ElemType>; if (!std::holds_alternative<std::vector<std::string>>(var)) { throw std::runtime_error("Expected string vector for element type"); } return extract_vector<InnerType>(std::get<std::vector<std::string>>(var), orch); } } // 核心实现:遍历tuple索引构建元素 template<typename T, std::size_t... Is> typename T::DataKV_tuple get_tuple_impl(const std::unordered_map<int, std::variant<std::string, std::vector<std::string>>>& mapping, const Orchestrator<T>& orch, std::index_sequence<Is...>) { return std::make_tuple( process_mapping_item<std::tuple_element_t<Is, typename get_foo_types<typename T::Foo_interface>::types>>( mapping.at(Is), orch )... ); } // 对外接口函数 template <typename T> typename T::DataKV_tuple get_tuple(std::unordered_map<int, std::variant<std::string, std::vector<std::string>>> mapping, Orchestrator<T> orch) { using TupleType = typename T::DataKV_tuple; constexpr auto tuple_size = std::tuple_size_v<TupleType>; return get_tuple_impl<T>(mapping, orch, std::make_index_sequence<tuple_size>()); }
关键逻辑说明
- 类型提取:通过
get_foo_types模板特化,从Foo_interface中获取预定义的编译期类型列表,这是实现类型匹配的核心。 - 数据提取:
extract_single处理单个元素的提取,处理键不存在、类型不匹配的异常情况。extract_vector批量提取元素并组装成vector。
- 映射处理:
process_mapping_item通过constexpr编译期判断元素类型是单个对象还是vector,自动选择对应提取逻辑,并验证映射项的类型是否匹配。 - Tuple组装:利用
std::index_sequence遍历tuple的每个索引,为每个位置生成对应的元素,最终组装成目标tuple。 - 错误处理:所有运行时不匹配的情况(键不存在、类型不匹配、映射项类型错误)均抛出异常,符合需求中“允许执行失败”的要求。
内容的提问来源于stack exchange,提问作者user3641187
相关产品推荐
相关产品推荐

