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)和