Skip to content

CPlusPlus遍历模板参数包

chlict edited this page Apr 20, 2020 · 3 revisions

c++11中引入了变长模板(variadic templates):template <typename ...T>,由此可以向一个模板类传递任意长度、任意类型的类型参数。假设我们定义了一个接受变长模板参数的模板类,我们想检查传递进来的参数是否都是整数类型,那如何遍历这些参数进行一一检查呢?这里介绍两种方法。

1. 模板递归

通过模板特例化和模板递归,可以达到遍历的目的。首先定义一个一般化的版本,比如template <typename ...T> struct all_integral_t;。它不负责具体的判断任务,具体的判断由特化的版本完成。然后定义一个递归形态的特化版本以及递归终止的特化版本:

#include <type_traits>
#include <iostream>

// Declaration
template <typename ...T>
struct all_integral_t;

// Termination
template <typename T>
struct all_integral_t<T> {
    static constexpr bool value = std::is_integral<T>::value;
};

// Recursion
template <typename Head, typename ...Tails>
struct all_integral_t<Head, Tails...> {
    static constexpr bool value = std::is_integral<Head>::value && all_integral_t<Tails...>::value;
};

它的原理是这样的:在Recursion特化版本中,把传递进来的模板参数分成Head和Tails两部分,首先检查Head是不是integral类型,然后递归调用all_integral_t<Tails...>对Tails...部分进行检测。Tails...进而又被拆成Head'和Tails'...匹配给all_integral_t,此时Tails'比Tails就少了一个元素。重复下去,最终匹配到单参数的all_integral_t<T>版本,递归结束。

可以拿这段代码测试:

int main() {
    std::cout << all_integral_t<int>::value << std::endl;                // true
    std::cout << all_integral_t<int, short, long>::value << std::endl;   // true
    std::cout << all_integral_t<int, float, double>::value << std::endl; // false
}

2. 借助boost::hana的类型容器

boost::hana是一个元编程的库,它提供了异质容器,可以将类型不同的元素存放到同一个tuple中。hana还提供了操作tuple的各种接口,比如for_each, fold, filter, all_of等,可以让我们比较自然的访问tuple里的元素。通过下面一段简单的代码即可完成对变长模板的检查:

1 #include <boost/hana.hpp>
2 
3 template <typename ...T>
4 struct all_integral_t {
5     static constexpr auto types = boost::hana::tuple_t<T...>;
6     static constexpr auto value = boost::hana::all_of(types, [](auto &&v) {
7         using t = typename std::remove_reference_t<decltype(v)>::type;
8         return std::is_integral<t>::value;
9     }); 
10 };

第5行使用了hana::tuple_t<T...>来存储所有T...的信息,tuple_t的定义如下:

    template <typename ...T>
    constexpr hana::tuple<hana::type<T>...> tuple_t{};

它是一个模板变量,即可以接受模板参数的变量。接受模板参数后tuple_t<T...>是它的值,它的类型是hana::tuple<hana::type<T>...>,也就是由类型为hana::type<T>...的一系列变量为元素构成的tuple。暂时不管hana::type<T>是如何定义的,只要知道它里面有一个type成员记录了真实的T的类型即可。上述第6~9行就是遍历tuple里存放的所有元素,用一个lambda函数对每一个元素进行检查,第7行得到元素对应的T,用std::is_integral检查是否是整数,当所有元素满足条件,hana::all_of返回true。

总结

介绍了两种遍历变长模板参数包的方法。其中第二种借用boost::hana库的类型变量的容器,可以像循环一样遍历参数包,比第一种递归方式更为自然。