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;
};
template
struct types {};
如果定义了一组对types类型做操作的算法,那么我们就可以把参数包放入types中,然后对它做这样那样的事情。。
看到这里,不知道有没有朋友想起来很久很久以前,Loki库里的TypeList。现代的C++当然不需要再像当年那样用外敷类和繁琐的宏来实现这个,使用变参模板加模板元就好了。
有了上面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!");
};
/*
Return size
*/
template
struct types_size : std::integral_constant
, check_is_types
{};
template
struct types_size>
: std::integral_constant
{};
// 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的访问算法就是根据传入的索引(index)定位类型。我们可以先写下types_at的定义:
template
struct types_at : check_is_index_valid
{
using type = TypesT;
};
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的。
首先我们来考虑类型的连接。需求很简单,传入两个类型,把它们连接成一个types。
当参数是普通类型时的算法很简单:
template
struct types_link
{
using type = types;
};
template
struct types_link, U>
{
using type = types;
};
template
struct types_link>
{
using type = 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
插入算法的需求如下:
给定一个types,传入索引index和类型T,需要把T插入到types的index处。根据这个需求,我们可以先写出types_insert的定义:
template
struct types_insert : check_is_types
, check_is_index_valid
{
using type = TypesT;
};