You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何并行遍历两个模板参数包实现灵活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);
}

关键说明

  1. std::index_sequence的作用:std::index_sequence_for<Ts...>生成与Ts...长度一致的索引序列,通过Idx...展开后,可精准关联tuple中对应位置的vector和构造函数的对应参数。
  2. 统一转换逻辑:convert_to_vector函数封装了单个元素和范围类型的转换逻辑,避免重复代码。
  3. 静态断言:提前校验参数数量与转盘数量的一致性,在编译阶段发现错误。

内容的提问来源于stack exchange,提问作者A M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 12:05:33