C++实现tuple元素apply方法的函数复合式链式调用
问题说明
- 现有一个元素类型为
T1,...,TN的std::tuple,元组中每个类型都实现了apply()方法。 - 需要实现一个函数,接收该元组和一个初始值作为入参,返回从初始值开始链式调用所有元素
apply()方法的结果。
待补全的代码框架和测试用例如下:
template <typename... Args, typename Input> auto apply(std::tuple<Args...> const &tpl, Input x) { // return ??? } // 测试用例定义 struct Sqr { static int apply(int x) { return x * x; } }; enum class Choice { One, Two, }; struct Choose { static int apply(Choice choice) { switch (choice) { case Choice::One: return 1; case Choice::Two: return 2; } } }; void test() { auto tpl = std::tuple(Sqr{}, Choose{}); assert(apply(tpl, Choice::One) == 1); assert(apply(tpl, Choice::Two) == 4); }
之前尝试用折叠表达式、遍历tuple元素调用函数的方案都无法编译,核心问题是:需要将上一次apply()的返回值作为下一次调用的入参,而非所有调用都传入初始值。
错误实现示例,问题在于所有apply()调用都接收同一个初始值x,中间结果无法传递:
template <typename... Args, typename Input> auto apply(std::tuple<Args...> const &tpl, Input x) { return std::apply([&x](auto &&... args) { return (..., args.apply(x)); }, tpl); }
规则约束
- 调用顺序从元组最后一个元素向前执行,和数学函数复合规则一致,即
(f * g)(x) := f(g(x)) - 不强制约束元组内各元素
apply()方法的输入输出类型,仅要求相邻调用的输入输出类型可匹配即可。
实现方案
C++17 兼容版本
通过std::index_sequence倒序遍历tuple元素,配合逗号折叠表达式顺序执行调用,每次将上一次的返回值作为下一次的入参:
namespace detail { template <std::size_t... Is, typename Tuple, typename Input> auto apply_reverse_impl(std::index_sequence<Is...>, Tuple const& tpl, Input x) { constexpr auto tpl_size = sizeof...(Is); auto val = std::move(x); // 逗号左折叠:按Is从小到大顺序执行,对应tuple索引从大到小 ( (val = std::get<tpl_size - 1 - Is>(tpl).apply(std::move(val))), ... ); return val; } } template <typename... Args, typename Input> auto apply(std::tuple<Args...> const &tpl, Input x) { return detail::apply_reverse_impl( std::index_sequence_for<Args...>{}, tpl, std::move(x) ); }
逻辑说明
std::index_sequence_for<Args...>生成从0到N-1的索引序列(N为tuple元素个数)- 遍历索引时,通过
tpl_size -1 - Is将正序索引转换为倒序,保证第一个执行的是tuple最后一个元素的apply() - 逗号折叠表达式严格按从左到右顺序执行,每次把前一次的返回值移动传入下一次调用,避免不必要的拷贝
- 相邻调用类型只要可匹配即可正常编译,不需要统一所有
apply()的输入输出类型
C++20 简化版本
可以直接在lambda内使用模板参数,省略单独的辅助函数:
template <typename... Args, typename Input> auto apply(std::tuple<Args...> const &tpl, Input x) { return std::apply([](auto const&... fns, auto val) { constexpr auto tpl_size = sizeof...(fns); return [&]<std::size_t... Is>(std::index_sequence<Is...>) { auto res = std::move(val); auto tpl_ref = std::tie(fns...); ( (res = std::get<tpl_size - 1 - Is>(tpl_ref).apply(std::move(res))), ... ); return res; }(std::index_sequence_for<Args...>{}); }, tpl, std::move(x)); }
内容的提问来源于stack exchange,提问作者ransh
相关产品推荐
相关产品推荐

