如何并行遍历两个模板参数包实现灵活Odometer类构造函数?
问题描述
我正在实现一个高度灵活的Odometer类,它可包含多个转盘,每个转盘的值数量和数据类型均可不同。该类通过模板参数数量定义行为:
- 1个模板参数:如
Odometer<int>,内部数据类型为std::vector<std::vector<int>>,所有转盘值类型均为该参数类型; - 2个及以上模板参数:参数数量对应转盘数量,每个转盘值类型为对应模板参数,如
Odometer<char, int, double>的内部类型为std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>。
现在需要为其添加可变参数构造函数,需同时并行遍历类的模板参数包与构造函数的参数包,且不想使用递归模板。目前仅想到借助带std::index_sequence的辅助类,但不确定具体实现方式。以下为简化后的代码示例:
#include <vector> #include <tuple> #include <list> #include <initializer_list> template<typename...Ts> struct Odometer { static constexpr bool IsTuple = ((std::tuple_size<std::tuple<Ts...>>::value) > 1); template<typename...Ts> using Tuples = std::tuple<std::vector<Ts>...>; template<typename...Ts> using MyType = std::tuple_element_t<0, std::tuple<Ts...>>; template<typename...Ts> using Vectors = std::vector<std::vector<MyType<Ts...>>>; template<typename...Ts> using Disks = std::conditional<IsTuple, Tuples<Ts...>, Vectors<Ts...>>::type; Disks<Ts...> disks{}; template <typename...Args> Odometer(Args...args) { if constexpr (IsTuple) { // Here disk is a std::tuple<std::vector<char>, std::vector<int>, std::vector<double>> ([&] { //std::vector<MyType<Ts...>> disk{}; // Does not work. Or would always be a std::vector<char> if constexpr (std::ranges::range<Args>) { //for (const auto& r : args) // Does not work //disk.push_back(r); // Does not work } else { //disk.push_back(args); // Does not work } } (), ...); } else { ([&] { disks.push_back({}); if constexpr (std::ranges::range<Args>) { for (const auto& r : args) disks.back().push_back(r); } else { disks.back().push_back(args); } } (), ...); } } }; int main() { Odometer<char, int, double> odo2('a', std::vector{1,2,3}, std::list{4.4, 5.5}); }
核心问题:如何实现两个参数包(类模板参数包Ts...和构造函数参数包Args...)的并行遍历?
解决方案
要实现两个参数包的并行遍历,不需要递归,借助std::index_sequence和辅助函数就能完成。核心思路是用索引序列关联两个参数包中对应位置的元素,逐个处理每一组Ts[i]和Args[i]。
步骤1:修复类型别名的命名冲突
原代码中模板别名复用了Ts...作为参数,会遮蔽类的模板参数包,先修正这个问题:
template<typename...Ts> struct Odometer { static constexpr bool IsTuple = sizeof...(Ts) > 1; // 更简洁的判断方式 // 修正模板别名的参数名,避免遮蔽类的Ts... template<typename...Us> using Tuples = std::tuple<std::vector<Us>...>; template<typename U> using Vectors = std::vector<std::vector<U>>; using Disks = std::conditional_t<IsTuple, Tuples<Ts...>, Vectors<std::tuple_element_t<0, std::tuple<Ts...>>>>; Disks disks{}; // 后续构造函数实现... };
步骤2:实现带索引序列的辅助构造逻辑
添加私有辅助函数,利用std::index_sequence并行访问两个参数包的对应元素:
private: // 通用转换函数:将单个元素或范围转换为对应类型的vector template<typename T, typename Arg> std::vector<T> convert_to_vector(Arg&& arg) { if constexpr (std::ranges::range<std::decay_t<Arg>>) { return std::vector<T>(std::ranges::begin(arg), std::ranges::end(arg)); } else { return {static_cast<T>(std::forward<Arg>(arg))}; } } // 处理Tuple版本的辅助函数:逐个初始化tuple中的vector template<typename...Args, std::size_t...Idx> void init_tuple(std::index_sequence<Idx...>, Args&&...args) { // 对每个索引Idx,取出tuple中第Idx个vector,用args[Idx]初始化 (std::get<Idx>(disks) = convert_to_vector<Ts>(std::forward<Args>(args)), ...); }
步骤3:重构构造函数逻辑
在构造函数中根据类型分支调用对应逻辑,并添加参数数量校验:
public: template <typename...Args> Odometer(Args&&...args) { static_assert(sizeof...(Args) == sizeof...(Ts), "参数数量必须与转盘数量匹配"); if constexpr (IsTuple) { // 生成索引序列,调用辅助函数处理tuple init_tuple(std::index_sequence_for<Ts...>{}, std::forward<Args>(args)...); } else { // 单个模板参数的情况,复用转换函数简化逻辑 using ValueType = std::tuple_element_t<0, std::tuple<Ts...>>; ([&] { disks.push_back(convert_to_vector<ValueType>(std::forward<Args>(args))); }(), ...); } }
完整代码整合
把上述修正整合后,完整可运行代码如下:
#include <vector> #include <tuple> #include <list> #include <initializer_list> #include <ranges> #include <utility> template<typename...Ts> struct Odometer { static constexpr bool IsTuple = sizeof...(Ts) > 1; template<typename...Us> using Tuples = std::tuple<std::vector<Us>...>; template<typename U> using Vectors = std::vector<std::vector<U>>; using Disks = std::conditional_t<IsTuple, Tuples<Ts...>, Vectors<std::tuple_element_t<0, std::tuple<Ts...>>>>; Disks disks{}; private: template<typename T, typename Arg> std::vector<T> convert_to_vector(Arg&& arg) { if constexpr (std::ranges::range<std::decay_t<Arg>>) { return std::vector<T>(std::ranges::begin(arg), std::ranges::end(arg)); } else { return {static_cast<T>(std::forward<Arg>(arg))}; } } template<typename...Args, std::size_t...Idx> void init_tuple(std::index_sequence<Idx...>, Args&&...args) { (std::get<Idx>(disks) = convert_to_vector<Ts>(std::forward<Args>(args)), ...); } public: template <typename...Args> Odometer(Args&&...args) { static_assert(sizeof...(Args) == sizeof...(Ts), "参数数量必须与转盘数量匹配"); if constexpr (IsTuple) { init_tuple(std::index_sequence_for<Ts...>{}, std::forward<Args>(args)...); } else { using ValueType = std::tuple_element_t<0, std::tuple<Ts...>>; ([&] { disks.push_back(convert_to_vector<ValueType>(std::forward<Args>(args))); }(), ...); } } }; int main() { // 测试多模板参数情况 Odometer<char, int, double> odo2('a', std::vector{1,2,3}, std::list{4.4, 5.5}); // 测试单模板参数情况 Odometer<int> odo1(1, std::vector{2,3}, 4); }
关键说明
std::index_sequence的作用:std::index_sequence_for<Ts...>生成与Ts...长度一致的索引序列,通过Idx...展开后,可精准关联tuple中对应位置的vector和构造函数的对应参数。- 统一转换逻辑:
convert_to_vector函数封装了单个元素和范围类型的转换逻辑,避免重复代码。 - 静态断言:提前校验参数数量与转盘数量的一致性,在编译阶段发现错误。
内容的提问来源于stack exchange,提问作者A M
相关产品推荐
相关产品推荐

