如何编写支持模板参数自动推导的旋转参数调用函数模板?
实现支持自动模板参数推导的
call_rotated函数 问题背景
需要编写一个函数模板,调用目标函数时将传入参数按旋转顺序传递:把最后一个参数作为目标函数的第一个参数。现有实现能完成功能,但必须手动指定PARAMETERS模板参数(如call_rotated<int, int>),希望实现无需手动指定、支持自动推导的版本。
原实现的问题分析
原代码的模板参数推导失败,核心原因是C++不支持将非末尾的参数包自动推导:
template <typename ...PARAMETERS, typename LAST, typename FN> void call_rotated(FN fn, PARAMETERS &&...parameters, LAST &&last) { fn(std::forward<LAST>(last), std::forward<PARAMETERS>(parameters)...); }
这里PARAMETERS...位于函数参数列表的中间(FN fn之后,LAST &&last之前),编译器无法确定哪些参数属于PARAMETERS...,哪些属于LAST,因此必须手动指定模板参数。
解决方案:支持自动推导的实现
我们可以利用C++17及以上的特性,通过两种方式实现自动推导,代码如下:
方案1:基于std::tuple和std::apply(简洁直观)
#include <utility> #include <tuple> #include <cstdio> template <typename FN, typename... Args> void call_rotated(FN fn, Args&&... args) { // 处理无参数的边界情况 if constexpr (sizeof...(Args) == 0) { fn(); return; } // 将所有参数打包为tuple auto args_tuple = std::make_tuple(std::forward<Args>(args)...); // 用std::apply展开tuple,lambda自动匹配前N-1个参数和最后一个参数 std::apply( [&fn](auto&&... front_args, auto&& last_arg) { fn(std::forward<decltype(last_arg)>(last_arg), std::forward<decltype(front_args)>(front_args)...); }, args_tuple ); } // 测试用函数 void print(const char *l, int a, int b) { printf("%s %d %d\n", l, a, b); } int main() { // 无需手动指定模板参数,编译器自动推导 call_rotated(&print, 1, 2, "was_last"); }
原理:
- 把所有传入参数打包成
std::tuple,此时参数包Args...位于函数参数列表末尾,编译器可以自动推导。 - 通过
std::apply将tuple展开,lambda的参数列表中,front_args...会匹配tuple中除最后一个外的所有元素,last_arg匹配最后一个元素,最终按要求顺序调用目标函数。
方案2:基于std::index_sequence(更底层的实现)
#include <utility> #include <tuple> #include <cstdio> namespace detail { // 内部实现函数:利用索引序列拆分参数 template <typename FN, typename... Args, std::size_t... Idx> void call_rotated_impl(FN fn, std::index_sequence<Idx...>, std::tuple<Args...>&& args) { // 取出最后一个参数,再依次取出前N-1个参数 fn(std::get<sizeof...(Args)-1>(std::move(args)), std::get<Idx>(std::move(args))...); } } template <typename FN, typename... Args> void call_rotated(FN fn, Args&&... args) { if constexpr (sizeof...(Args) == 0) { fn(); return; } // 生成前N-1个参数的索引序列(0到sizeof...(Args)-2) using Indices = std::make_index_sequence<sizeof...(Args)-1>; detail::call_rotated_impl(fn, Indices{}, std::make_tuple(std::forward<Args>(args)...)); } // 测试用函数和main函数同方案1
原理:
- 通过
std::make_index_sequence生成前N-1个参数的索引,将参数打包为tuple后,按索引取出对应元素,最后一个元素通过固定索引sizeof...(Args)-1获取,再按要求顺序调用目标函数。
总结
两种方案都解决了原实现的手动指定模板参数问题,编译器可以自动推导FN和Args...模板参数,完全满足需求。推荐方案1,代码更简洁易读,依赖C++17及以上标准。
内容的提问来源于stack exchange,提问作者geza
相关产品推荐
相关产品推荐

