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

如何延迟展开C++参数包?元组zip场景下的技术问询

实现任意数量std::tuple的zip操作:延迟展开参数包的优雅方案

我正在研究std::tuple的用法,想实现任意数量元组的zip操作。目前的解决方案不够理想,过程中碰到一个技术难点:

有时候需要分别展开参数包,但这些参数包会同时作为模板参数和函数参数出现在同一个调用里。我找不到一种直观的方法,能单独展开Is参数包而不展开ts,还不用先把ts打包成std::tuple再用std::get<I>解包。

显然把一个函数拆成五个不是好办法,我知道可以用lambda,但不确定会不会更简洁。有没有优雅的方式延迟展开ts参数包?

原代码示例

#include <iostream>
#include <tuple>
#include <string_view>

template <typename T, typename... Ts>
auto get_first(T t, Ts...) {
    return t;
}

template <size_t I, typename... Ts>
auto zip2_impl4(Ts... ts) {
    return std::make_tuple(std::get<I>(ts)...);
}

template <size_t I, typename T, size_t... Is>
auto zip2_impl3(T t, std::index_sequence<Is...>) {
    return zip2_impl4<I>(std::get<Is>(t)...);
}

template <size_t I, typename T>
auto zip2_impl2(T t) {
    using size = std::tuple_size<T>;
    using seq = std::make_index_sequence<size::value>;
    return zip2_impl3<I>(t, seq{});
}

template <size_t... Is, typename... Ts>
auto zip2_impl(std::index_sequence<Is...> seq, Ts... ts) {
    // need to defer expanding the pack ts,
    // because the packs Is and ts need to expand separately
    auto t = std::make_tuple(ts...);
    return std::make_tuple(zip2_impl2<Is>(t)...);
}

template <typename... Ts>
auto zip2(Ts... ts) {
    using size = std::tuple_size<decltype(get_first(ts...))>;
    using seq = std::make_index_sequence<size::value>;
    return zip2_impl(seq{}, ts...);
}

int main() {
    using namespace std::literals;
    auto ints = std::make_tuple(1,2,3);
    auto svs1 = std::make_tuple("a"sv, "b"sv, "c"sv);
    auto svs2 = std::make_tuple("d"sv, "e"sv, "f"sv);
    auto zipped = zip2(ints, svs1, svs2);

    std::apply([](auto... args) {
        (std::apply([](auto... args) {
            ((std::cout << args), ...);
        }, args), ...);
    }, zipped);

    return 0;
}

原代码输出

1ad2be3cf

优雅的解决方案

我们可以利用C++17的折叠表达式和模板参数推导,直接简化逻辑,避免多层辅助函数,同时完美实现参数包的延迟展开:

#include <iostream>
#include <tuple>
#include <string_view>
#include <type_traits>

template <size_t... Is, typename... Ts>
auto zip_impl(std::index_sequence<Is...>, Ts&&... ts) {
    // 外层展开Is参数包,内层对每个Is展开ts参数包
    return std::make_tuple(
        std::make_tuple(std::get<Is>(std::forward<Ts>(ts))...)
        ...
    );
}

template <typename... Ts>
auto zip(Ts&&... ts) {
    // 取第一个元组的大小作为zip后的元素数量
    using FirstTuple = std::decay_t<decltype((ts, ...))>;
    constexpr size_t TupleSize = std::tuple_size_v<FirstTuple>;
    return zip_impl(std::make_index_sequence<TupleSize>{}, std::forward<Ts>(ts)...);
}

int main() {
    using namespace std::literals;
    auto ints = std::make_tuple(1,2,3);
    auto svs1 = std::make_tuple("a"sv, "b"sv, "c"sv);
    auto svs2 = std::make_tuple("d"sv, "e"sv, "f"sv);
    auto zipped = zip(ints, svs1, svs2);

    std::apply([](auto... args) {
        (std::apply([](auto... elems) {
            ((std::cout << elems), ...);
        }, args), ...);
    }, zipped);

    return 0;
}

方案解析

  1. 延迟展开的核心逻辑:在zip_impl中,外层的...会逐个遍历Is中的每个索引;对每个索引Is,内层的...会遍历所有元组ts,取出每个元组中第Is个元素组成新元组。这就实现了先固定Is、再展开ts的需求,完全不需要额外把ts打包成临时元组。
  2. 简化结构:去掉了原代码中多层嵌套的辅助函数,只用两个函数就完成了所有逻辑,代码可读性更高。
  3. 性能优化:使用std::forward完美转发参数,避免不必要的拷贝操作。

优化后输出

1ad2be3cf

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 15:26:08