C++20中如何实现同时处理多个tuple的multi_apply函数
设计C++20的multi_apply函数:简化多tuple的元素级操作
问题背景
在C++20中,当需要对多个同大小的tuple执行元素级操作时,嵌套使用std::apply会随着tuple数量增多变得冗长且难以维护。比如处理两个tuple的场景:
#include <tuple> #include <type_traits> auto operation(auto& foo, auto& bar) { // 自定义任意操作 return foo + bar; } int main() { std::tuple<int, float, double> Foo{1, 2.0f, 3.0}; std::tuple<float, bool, char> Bar{1.0f, true, 'a'}; auto result = std::apply( [&](auto &&...foo) { return std::apply([&](auto &&...bar) { return std::make_tuple(operation(foo, bar)...); }, Bar); }, Foo); return 0; }
当tuple数量增加到3个及以上时,嵌套的std::apply会层层嵌套,代码可读性和维护性急剧下降。
需求目标
我们需要实现一个multi_apply函数,避免嵌套调用,支持简洁的调用方式,自动将多个tuple的对应位置元素组合起来执行操作,最终生成结果tuple。
注:所有输入tuple的大小必须一致,操作的入参数量与tuple数量相等。
实现方案
方案1:直接支持元素级操作的multi_apply
这个方案针对最常见的场景:对每个位置i,调用operation(tuple1[i], tuple2[i], ..., tupleN[i]),并将所有结果收集为tuple。
#include <tuple> #include <utility> #include <type_traits> // 辅助函数:处理单个索引的元素调用 template<typename Op, typename... Tuples, std::size_t I> constexpr auto apply_at_index(Op&& op, Tuples&&... tuples) { return std::forward<Op>(op)(std::get<I>(std::forward<Tuples>(tuples))...); } // 核心multi_apply实现 template<typename Op, typename... Tuples> constexpr auto multi_apply(Op&& op, Tuples&&... tuples) { // 静态断言所有tuple大小一致 static_assert(((std::tuple_size_v<std::decay_t<Tuples>> == std::tuple_size_v<std::decay_t<decltype(tuples[0])>>) && ...), "All tuples must have the same size"); constexpr std::size_t TupleSize = std::tuple_size_v<std::decay_t<decltype(tuples[0])>>; // 通过索引序列遍历每个位置 return [&]<std::size_t... Is>(std::index_sequence<Is...>) { return std::make_tuple(apply_at_index<Op, Tuples..., Is>(std::forward<Op>(op), std::forward<Tuples>(tuples)...)...); }(std::make_index_sequence<TupleSize>{}); } // 测试示例 auto operation(auto&... elems) { // 自定义任意操作,这里用折叠表达式求和 return (elems + ...); } int main() { std::tuple<int, float, double> Foo{1, 2.0f, 3.0}; std::tuple<float, bool, char> Bar{1.0f, true, 'a'}; std::tuple<char, float, char> Baz{'1', 3.0f, 'b'}; // 简洁调用multi_apply auto result = multi_apply(operation, Foo, Bar, Baz); return 0; }
方案2:更通用的multi_apply(传递元素参数包)
如果你需要更灵活地处理所有tuple的元素(而不仅仅是对应位置),可以实现一个将所有tuple的元素展开为单个参数包的版本:
#include <tuple> #include <utility> #include <type_traits> namespace detail { template<typename F, typename... Tuples, std::size_t... Is> constexpr auto multi_apply_impl(F&& f, std::index_sequence<Is...>, Tuples&&... tuples) { return std::forward<F>(f)(std::get<Is>(std::forward<Tuples>(tuples))... ...); } } template<typename F, typename... Tuples> constexpr auto multi_apply(F&& f, Tuples&&... tuples) { constexpr std::size_t TupleSize = std::tuple_size_v<std::decay_t<decltype(tuples[0])>>; static_assert(((std::tuple_size_v<std::decay_t<Tuples>> == TupleSize) && ...), "All tuples must have the same size"); return detail::multi_apply_impl( std::forward<F>(f), std::make_index_sequence<TupleSize>{}, std::forward<Tuples>(tuples)... ); } // 测试示例 int main() { std::tuple<int, float> t1{1, 2.0f}; std::tuple<double, bool> t2{3.0, true}; // 回调接收所有展开的元素:1, 2.0f, 3.0, true auto result = multi_apply([](auto... elems) { return std::make_tuple((elems * 2)...); }, t1, t2); return 0; }
关键点说明
- 静态断言:确保所有输入tuple的大小一致,提前规避运行时错误。
- 索引序列:利用
std::index_sequence和C++20模板lambda,简洁遍历tuple的每个位置。 - 完美转发:通过
std::forward保持元素的左值/右值属性,避免不必要的拷贝。
内容的提问来源于stack exchange,提问作者Anon232
相关产品推荐
相关产品推荐

