模板参数遍历 | C & C++
本文最后更新于:2022年8月8日
普通写法
void expand() {}
template <typename T, typename... Args>
void expand(T arg, Args... rest) {
std::cout << typeid(arg).name() << std::endl;
expand(rest...);
}
这种写法比较容易理解:通过递归的方式展开所有参数。当函数参数不为空时,会匹配到第二个参数,当函数参数为空时,会匹配到第一个参数。
封装写法
下面看一种抽象得比较好的写法:
template <size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if_t<I == sizeof...(Tp)> expand(
std::tuple<Tp...> &t, FuncT f) {}
template <size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if_t <
I<sizeof...(Tp)> expand(std::tuple<Tp...> &t, FuncT f) {
f(std::get<I>(t));
expand<I + 1, FuncT, Tp...>(t, f);
}
auto args_tup = std::forward_as_tuple(args...);
expand(args_tup, [](auto arg) {
std::cout << typeid(arg).name() << std::endl;
});
这种写法把对每个参数的处理单独抽象成一个参数,只需传入一个函数作为参数,这个函数就是要对每个参数进行的处理。
这种写法看起来有点复杂,其实原理和前面那种是一样的,也是通过递归的方式对所有参数依次处理。这里涉及到了一个编译期函数 std::enable_if_t
,先来解释下这个函数的作用,还是先看示例:
// 如果 T 的类型是 int,则定义函数 int read(void* = nullptr);否则不定义该函数
template <typename T>
T read(typename std::enable_if_t<std::is_same_v<T, int> > * = nullptr) {
return 42;
}
// 如果 T 的类型是 double,则定义函数 double read();否则不定义该函数
template <typename T>
typename std::enable_if_t<std::is_same_v<T, double>, T> read() {
return 3.14;
}
可以看到如果函数 std::enable_if_t
的模板参数只有一个,则作为定义该函数与否的判断;如果模板参数有两个,则成功匹配后整个函数等价于第二个模板参数。
了解了函数 std::enable_if_t
的用法后,再分析上述模板参数遍历的实现原理:
- 把可变模板参数转成一个 tuple,对该 tuple 进行递归
- 可以看出两个函数中的
std::enable_if_t
不会同时成立,因为sizeof...(Tp)
的值是固定的 - 在第一次匹配时,如果 tuple 的大小不为 0,则第二个函数的
std::enable_if_t
的模板参数为 true,即匹配到第二个函数,取到模板参数的第一个放到函数f
中执行 - 在整个匹配过程中,
I
的取值为 0、1、2、…、n(n = sizeof…(Tp)),函数的匹配情况会根据std::enable_if_t
的模板参数是否为 true 而来,即I
小于 n 时,匹配第二个函数,I
等于 n 时,匹配第一个函数(作为递归的终点),最终完成对所有函数的处理
参考
评论系统采用 utterances ,加载有延迟,请稍等片刻。