在 C++17 之后,利用折叠表达式和递归模板元编程,可以轻松实现将可变参数模板的类型序列或参数列表按逆序排列。下面给出两种常见需求的实现:一种是对类型列表进行反转,另一种是对函数参数包进行逆序调用。
1. 对类型列表进行反转
1.1 关键点
- 类型列表:用
std::tuple或自定义的type_list存储类型序列。 - 递归拆分:将第一个类型取出,递归处理剩余类型,最后在拼接阶段把第一个类型放到尾部。
- C++17 语法糖:使用
decltype、折叠表达式和auto进行更简洁的实现。
1.2 代码实现
#include <tuple>
#include <type_traits>
#include <iostream>
// 自定义类型列表
template<typename... Ts> struct type_list {};
// 递归反转
template<typename TL>
struct reverse;
// 递归终止:空列表
template<>
struct reverse<type_list<>> {
using type = type_list<>;
};
// 递归步骤:拆分第一个类型
template<typename T, typename... Rest>
struct reverse<type_list<T, Rest...>> {
using type = typename concat<type_list<Rest...>, type_list<T>>::type;
};
// 合并两个类型列表
template<typename TL1, typename TL2>
struct concat;
template<typename... Ts1, typename... Ts2>
struct concat<type_list<Ts1...>, type_list<Ts2...>> {
using type = type_list<Ts1..., Ts2...>;
};
// 用法示例
using original = type_list<int, double, char, std::string>;
using reversed = reverse <original>::type;
int main() {
// 通过静态断言验证
static_assert(std::is_same_v<reversed, type_list<std::string, char, double, int>>);
std::cout << "类型列表已反转!\n";
}
说明
concat用于把两段类型列表拼接。reverse通过拆分第一个类型并递归处理剩余部分,最终把拆出的类型放到结果列表末尾。- 采用
static_assert可在编译期验证结果正确。
2. 对可变参数函数调用进行逆序
2.1 场景
你可能希望在某些库中,使用可变参数模板实现一个 print 函数,输出参数时保持原来的顺序,但你想要逆序打印。或更一般地,想把一个函数包 f 以逆序依次应用到参数列表上。
2.2 逆序调用实现
#include <iostream>
#include <utility>
// 递归终止:空参数包
void reverse_print() {
std::cout << "完成逆序打印。\n";
}
// 递归步骤:先打印后面,再打印当前
template<typename T, typename... Rest>
void reverse_print(T&& first, Rest&&... rest) {
// 递归打印剩余
reverse_print(std::forward <Rest>(rest)...);
// 再打印当前
std::cout << std::forward<T>(first) << ' ';
}
// 逆序执行任意可变参数函数
template<typename Func, typename... Args>
auto reverse_apply(Func&& f, Args&&... args) {
// 先展开剩余参数
return reverse_apply_impl(std::forward <Func>(f),
std::index_sequence_for<Args...>{},
std::forward <Args>(args)...);
}
// 内部实现:通过索引序列逆序调用
template<typename Func, std::size_t... Is, typename... Args>
auto reverse_apply_impl(Func&& f,
std::index_sequence<Is...>,
Args&&... args) {
// 通过折叠表达式逆序调用
return ((f(std::get <Is>(std::forward_as_tuple(args...)))), ...);
}
// 示例函数
void foo(int a) { std::cout << "foo(" << a << ")\n"; }
int main() {
// 逆序打印
reverse_print(1, 2.5, "hello", std::string("world"));
// 逆序执行 foo
reverse_apply(foo, 10, 20, 30);
}
关键点
reverse_print先递归处理剩余参数,再打印当前,从而实现逆序。reverse_apply利用std::index_sequence生成索引序列,然后通过折叠表达式((f(...)), ...)按逆序调用f。- 这两种方式都保持了 完美转发(
std::forward),兼容左值、右值。
3. 小结
- 类型列表反转:通过递归拆分和拼接实现,适用于编译期类型操作。
- 参数包逆序调用:利用递归或折叠表达式实现,适用于运行时函数调用。
- 两种技术都展示了 C++ 模板元编程的强大与灵活。希望你在自己的项目中能灵活运用这些模式。