也谈编译期操作

说起模板元编程,估计不少人的第一反应都是充斥着各种递归的奇技淫巧,没错,这次我们就来对模板元这种屠龙之术进行初步窥探,看看能玩出什么花样出来。

小试牛刀

template <typename _Tp, typename... args>
struct get_size {
    static constexpr std::size_t value = get_size<args...>::value + 1;
};

template <typename _Tp>
struct get_size<_Tp> {
    static constexpr std::size_t value = 1;
};

get_size用于获取参数类型列表的类型个数(可以包括重复的类型),因为要用到递归实现,所以很容易推出f(n) = f(n - 1) + 1的结构。不过递归要写终结条件,这里假定最少的参数个数为1,只需要对模板参数类型为1的情况进行特化。如果有一定的基础,上面这段代码是不难理解的。

再来一个

template <bool __Cond, bool... args>
struct all_of {
    static constexpr bool value = __Cond && all_of<args...>::value;
};

template <bool __Cond>
struct all_of<__Cond> {
    static constexpr bool value = __Cond;
};

这个模板可用于判断参数列表的所有bool值是否都为真,若为真则true,反之false;其实和上面的也差不多,没有用到多复杂的递归。

同理,any_of也是类似的。

template <bool __Cond, bool... args>
struct any_of {
    static constexpr bool value = __Cond || any_of<args...>::value;
};

template <bool __Cond>
struct any_of<__Cond> {
    static constexpr bool value = __Cond;
};

上点强度

template <std::size_t __first, std::size_t __second, std::size_t... args>
struct is_ascending {
    static constexpr bool value = (__first <= __second) && is_ascending<__second, args...>::value;
};

template <std::size_t __first, std::size_t __second>
struct is_ascending<__first, __second> {
    static constexpr bool value = (__first <= __second);
};

判断模板参数序列是否是递增的(非严格递增),类似地,还有

template <std::size_t __first, std::size_t __second, std::size_t... args>
struct is_strictly_ascending {
    static constexpr bool value = (__first < __second) &&
    is_strictly_ascending<__second, args...>::value;
};

template <std::size_t __first, std::size_t __second>
struct is_strictly_ascending<__first, __second> {
    static constexpr bool value = (__first < __second);
};


template <std::size_t __first, std::size_t __second, std::size_t... args>
struct is_descending {
    static constexpr bool value = (__first >= __second) && is_descending<__second, args...>::value;
};

template <std::size_t __first, std::size_t __second>
struct is_descending<__first, __second> {
    static constexpr bool value = (__first >= __second);
};


template <std::size_t __first, std::size_t __second, std::size_t... args>
struct is_strictly_descending {
    static constexpr bool value = (__first > __second) && 
                                      is_strictly_descending<__second, args...>::value;
};

template <std::size_t __first, std::size_t __second>
struct is_strictly_descending<__first, __second> {
    static constexpr bool value = (__first > __second);
};

进阶

std::tuple和std::variant都是C++当中接受可变模板参数的容器。试想一下如何实现这种操作:std::tuple<int, double>, std::tuple<char, float>,合并之后变成了std::tuple<int, double, char, float>。

template <typename... args1, typename... args2>
struct tuple_type_cat
{};

如果出现了诸如此类的声明,会发现编译器报错,原因是可变参数必须在后面。那难道就没有办法了吗,其实还是有的,得"套中套"。

template <typename... args>
struct tuple_type_cat;

template <typename... args1, typename... args2>
struct tuple_type_cat<std::tuple<args1...>, std::tuple<args2...>> {
    using type = std::tuple<args1..., args2...>;
};

可以先声明一个可变参数模板的结构体,然后到具体实现的时候,这样就可以使用两个可变模板参数了。可以简单理解为,一个可变模板参数可以展开成两个可变模板参数。这样就可以避开声明的时候使用两个可变模板参数从而出现报错的情况。

std::variant也一样,来编写测试看看。

// variant_type_cat
template <typename... args>
struct variant_type_cat;

template <typename... args1, typename... args2>
struct variant_type_cat<std::variant<args1...>, std::variant<args2...>> {
    using type = std::variant<args1..., args2...>;       
};

int main()
{
    using v1 = std::variant<int, double>;
    using v2 = std::variant<char, float>;
    using v3 = variant_type_cat<v1, v2>::type;
    using tmp = std::variant<int, double, char, float>;

    static_assert(std::is_same_v<v3, tmp>);
}

下次有时间再深剖更多有关模板元的技术。