如何用参数包泛化多参数let函数?编译耗时疑问
问题:泛化支持任意参数数量的let函数
需求是将一组固定参数数量的let函数泛化为支持任意参数数量的版本,核心难点是将索引序列与参数的输出类型配对,生成对应的Var实例并传递给函数。
原固定参数版本的实现如下:
template <typename Fun, typename A> auto let(Fun fun, A a) { auto var0 = Var<0, get_out_type_t<A>>(); return LetN(fun(var0), a); } template <typename Fun, typename A, typename B> auto let(Fun fun, A a, B b) { auto var0 = Var<0, get_out_type_t<A>>(); auto var1 = Var<1, get_out_type_t<B>>(); return LetN(fun(var0, var1), a, b); } template <typename Fun, typename A, typename B, typename C> auto let(Fun fun, A a, B b, C c) { auto var0 = Var<0, get_out_type_t<A>>(); auto var1 = Var<1, get_out_type_t<B>>(); auto var2 = Var<2, get_out_type_t<C>>(); return LetN(fun(var0, var1, var2), a, b, c); } // 更多参数数量的重载...
我的失败尝试代码:
template <typename Fun, typename... Args> auto let2(Fun fun, Args... args) { using ArgsTuple = std::tuple<Args...>; auto g = [&](auto... indices){ return std::make_tuple(Var<indices, typename std::tuple_element<indices, ArgsTuple>::OutType>()...); }; auto vars = std::apply(g, std::index_sequence_for<Args...>()); return LetN(fun(vars), args...); }
编译时出现以下错误:
[...]/usr/include/c++/v1/tuple:1528:52: error: implicit instantiation of undefined template 'std::tuple_size<std::integer_sequence<unsigned long, 0, 1>>' _LIBCPP_INLINE_VAR constexpr size_t tuple_size_v = tuple_size<_Tp>::value; [...]/usr/include/c++/v1/tuple:1548:39: error: non-type template argument is not a constant expression typename __make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>::type{})
另外还有一个疑问:即便泛化版本实现成功,相比固定参数数量的重载函数,是否会增加编译耗时?
解决方案
1. 泛化版本的正确实现
你的尝试失败有两个核心原因:
std::apply的作用对象是tuple,但你传入的是std::index_sequence,它不属于tuple类型,导致std::tuple_size无法匹配,触发编译错误。- 就算生成了
tuple<Var...>,fun(vars)是把整个tuple作为单个参数传给fun,但原代码中fun需要接收多个独立的Var参数,调用方式不匹配。
下面是两种可行的泛化实现:
方式一:C++17及以上版本(辅助函数)
#include <tuple> #include <utility> // 内部辅助函数,处理索引序列展开 template <typename Fun, typename... Args, std::size_t... Indices> auto let_impl(Fun&& fun, std::tuple<Args...>&& args_tuple, std::index_sequence<Indices...>) { return LetN( std::forward<Fun>(fun)(Var<Indices, get_out_type_t<Args>>()...), std::forward<Args>(std::get<Indices>(args_tuple))... ); } // 对外接口 template <typename Fun, typename... Args> auto let(Fun&& fun, Args&&... args) { return let_impl( std::forward<Fun>(fun), std::make_tuple(std::forward<Args>(args)...), std::index_sequence_for<Args...>() ); }
方式二:C++20及以上版本(模板lambda,更简洁)
#include <utility> template <typename Fun, typename... Args> auto let(Fun&& fun, Args&&... args) { // 利用模板lambda直接捕获索引序列 return [&]<std::size_t... Indices>(std::index_sequence<Indices...>) { return LetN( std::forward<Fun>(fun)(Var<Indices, get_out_type_t<Args>>()...), std::forward<Args>(args)... ); }(std::index_sequence_for<Args...>()); }
这两种实现都能正确生成对应索引和类型的Var实例,并将它们作为独立参数传递给fun,完全匹配原固定版本的行为。
2. 编译耗时对比
泛化版本相比固定重载的编译耗时差异很小,具体情况如下:
- 差异来源:固定重载是针对每个参数数量生成独立的模板实例,泛化版本需要编译器处理参数包展开和索引序列推导,但现代编译器(如GCC、Clang)对这类模板元编程的优化已经非常成熟,额外开销可以忽略。
- 优势场景:如果你的代码中需要支持10个以上的参数数量,泛化版本反而会减少重复代码,降低编译器处理多个重载的总开销。
- 额外影响:表达式模板本身通常会带来一定的编译耗时,泛化版本的额外开销相比之下几乎可以忽略不计。
内容的提问来源于stack exchange,提问作者asynth
相关产品推荐
相关产品推荐

