使用模板元编程操作类型集合(C++11下的TypeList)

Wrote by mutouyun. (http://darkc.at/cxx-type-list/)

群里有个朋友要实现这么一个功能:如何在编译期把一个函数类型的参数减少一个。
简单来说,就是实现下面这个模板:

remove_func_par<2, void(int, long, short)>::type; // type = void(int, long)

根据输入的编译期整数,把函数参数表里对应的参数干掉一个。
为了实现这种功能,我们需要操作变参模板的参数包。比如像这样:

// make function's parameters from the types
 
template 
struct make_func_par;
 
template 
struct make_func_par>
{
    typedef R type(P...);
};
 
// remove function's parameter
 
template 
struct remove_func_par;
 
template 
struct remove_func_par
{
    using erase_pars_t = typename types_erase, N>::type;
    using type = typename make_func_par::type type;
};

上面这段代码的思想很简单,把模板参数包的第N个参数删掉,然后再将它重新展开成函数的参数表。而types的定义可以非常简单:

template 
struct types {};

如果定义了一组对types类型做操作的算法,那么我们就可以把参数包放入types中,然后对它做这样那样的事情。。

看到这里,不知道有没有朋友想起来很久很久以前,Loki库里的TypeList。现代的C++当然不需要再像当年那样用外敷类和繁琐的宏来实现这个,使用变参模板加模板元就好了。

一、types的判断和大小计算

有了上面types的定义之后,下面需要实现一些算法来操作它。首先,在不涉及到容器的查找修改时,最基本的算法简单来说有下面几个:判断容器类型(因为容器是编译期的一个类型)、计算容器大小、判断容器是否是空的。下面我们来依次实现它们。

判断算法非常简单:

/*
    Is types
*/
 
template 
struct is_types
     : std::false_type
{};
 
template 
struct is_types>
     : std::true_type
{};

有了判断的算法之后,对于后面的运算就可以在编译时判断出传入的类型是否符合要求。我们可以定义一个专门用来判断类型合法性的模板:

// Check is types or not
 
template 
struct check_is_types
{
    static_assert(is_types::value, "The template parameter is not a types-list!");
};

在需要的时候,继承check_is_types就好了。
接下来,是计算types的大小。在有了变参模板,以及针对模板参数包的sizeof运算符以后,这个工作也是非常简单的:

/*
    Return size
*/
 
template 
struct types_size : std::integral_constant
                  , check_is_types
{};
 
template 
struct types_size>
     : std::integral_constant
{};

通过继承check_is_types,types_size在传入参数不是一个types的时候,会在编译时报出错误提示。
有了计算types大小的工具,我们可以为后面的算法再准备两个编译时合法性判断的辅助类:

// Check is index valid or not
 
template 
struct check_is_index_valid
{
    static_assert(IndexN >= 0,                        "Index is out of range!");
    static_assert(IndexN < types_size::value, "Index is out of range!");
};
 
// Check is count valid or not
 
template 
struct check_is_count_valid
{
    static_assert(CountN > 0,                          "Count is too small!");
    static_assert(CountN <= types_size::value, "Count is too large!");
};

check_is_index_valid用来判断传入的索引是否超出了指定types的范围;
check_is_count_valid用来判断传入的大小是否超出了指定types的大小。
和check_is_types一样,在需要的时候继承这两个类模板就可以了。

然后,是容器是否为空的判断:

/*
    Test whether types is empty
*/
 
template 
struct types_empty : std::true_type
                   , check_is_types
{};
 
template 
struct types_empty>
     : std::false_type
{};
 
template <>
struct types_empty>
     : std::true_type
{};

二、types的元素访问

types的访问算法就是根据传入的索引(index)定位类型。我们可以先写下types_at的定义:

template 
struct types_at : check_is_index_valid
{
    using type = TypesT;
};

接下来,是思考如何通过模板元的递归定位元素了。在数学里,最基本的定位方法就是数个数(是的,你没听错,就是数数)。模板元在递归的时候,每次可以去掉参数包中开头的第一个参数,同时我们让传入的index减1。当index为0的时候,对应的参数类型就是我们需要的类型了。算法实现可以像这样:

template 
struct types_at, N>
     : types_at, N - 1>
{};
 
template 
struct types_at, 0>
{
    using type = T1;
};

上面的第一个types_at特化负责把参数包和index同时减1,并传入下一层;最后模板的递归会在第二个types_at特化处终结。
我们看到,这里并不需要一个types<>的特化。因为当传入的模板参数是types<>的时候,它不会匹配到任何一个特化,因此最初的types_at定义就可以搞定这种情况了。

有了types_at之后,我们可以很方便的实现front和back的定位算法:

/*
    Access first element
*/
 
template 
struct types_front
{
    using type = types_at_t;
};
 
/*
    Access last element
*/
 
template 
struct types_back
{
    using type = types_at_t::value - 1>;
};

三、types的连接(Link)和分配(Assign)

这两个算法都是用来把类型打包成types的。

首先我们来考虑类型的连接。需求很简单,传入两个类型,把它们连接成一个types。
当参数是普通类型时的算法很简单:

template 
struct types_link
{
    using type = types;
};

当两个类型都是普通类型时,算法是显然的。那么当其中一个类型是一个types时,另一个类型应该被追加到那个types的尾部或头部:

template 
struct types_link, U>
{
    using type = types;
};
 
template 
struct types_link>
{
    using type = types;
};

假如两个类型都是types类型,那么需要把它们连接成一个types:

template 
struct types_link, types>
{
    using type = types;
};

我们注意到,上面的link算法里考虑了当参数是types的情况。因此在做后面的其它算法时,通过使用这里的link,会把types内部的types展开。

下面是types的Assign算法。需求是,传入一个数字N和类型T,types_assign将构造一个由N个T组成的types。
有了上面的types_link以后,我们可以在模板递归中一次连接一个T,直到N减少到0为止。算法如下:

template 
struct types_assign
{
    static_assert(N >= 0, "N cannot be less than 0!");
private:
    using tail = typename types_assign::type;
public:
    using type = typename types_link::type;
};
 
template 
struct types_assign<0, T>
{
    using type = types<>;
};

由于使用了types_link连接types,当我们这样写时:types_assign<2, types>::type,将会得到:types

四、types的插入和删除

插入算法的需求如下:
给定一个types,传入索引index和类型T,需要把T插入到types的index处。根据这个需求,我们可以先写出types_insert的定义:

template 
struct types_insert : check_is_types
                    , check_is_index_valid
{
    using type = TypesT;
};

接下来考虑算法。插入算法其实只比数数多了

你可能感兴趣的:(C/C++,C++11,算法,TypeList,变参模板,模板元)