分配(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; };
接下来考虑算法。插入算法其实只比数数多了一个步骤,那就是在数到需要的位置后,把T插到那个位置。那么我们可以先写上数数的算法:
template
struct types_insert
, N, U> { private: using tail = typename types_insert
, N - 1, U>::type; public: using type = typename types_link
::type; };
每次递归,都将数出一个参数,并把剩下的继续向下传递。当所有的递归完成后,下一层的types_insert将返回一个已插入完毕的types,那么把这个types当做结尾,和T1连接在一起就好了。
关键的插入将在递归终结的时候完成:
template
struct types_insert
, 0, U> { using type = typename types_link
>::type; };
待插入的类型U,被插入到types的索引0处,也就是最开始的位置。
这里需要特殊考虑一下types<>:
template
struct types_insert
, 0, U> { using type = typename types_link
>::type; };
因为若不添加这个特化的话,types<>会被匹配到types_insert的定义上去,那么types<>将无法插入任何类型了。
可能有童鞋看到这里,觉得我们没必要把types
和types<>的特化分开写,直接这样就好了:
template
struct types_insert
, 0, U> { using type = typename types_link
>::type; };
看起来好像没问题,但实际上是不行的。这是因为
, 0, U>和
, N, U>之间存在二义性。当模板递归到最后一层时,N将为0,此时若types大小大于1,这两个特化都可以被匹配到。
而
, 0, U>和
, N, U>之间则没有二义性。因为前面的特化版本是后面一个的特殊情况。
这里也说明了模板元编程时书写的一个原则:应该从最普遍的特化版本开始,逐一特殊化各种条件,直到最后的递归终结。
这种书写方法可以保证不会出现模板特化的二义性,只是和数学归纳法的思考方向相反。如果习惯于用数学归纳法之类的方式思考模板元递归算法的童鞋,可以先正着写出算法,再倒着看每个条件是否是逐步特殊化的。
下面我们思考删除算法。需求:
给定一个types,传入索引index和数量count,需要把types中从索引index处开始的count个元素删除。
首先,我们还是先写出定义:
template
struct types_erase : check_is_types
, check_is_index_valid
{ using type = TypesT; };
同样的,删除算法也是在数到指定索引位置之后,将后面的元素删除掉。我们可以把count的需求放在一遍,先定位到需要删除的位置:
template
struct types_erase
, N, C> { private: using tail = typename types_erase
, N - 1, C>::type; public: using type = typename types_link
::type; };
和上面的插入一样,types_erase在递归后将返回一个处理完毕的types,之后把它和T1连起来就好了。
那么,当找到需要删除的索引时,我们自然是删掉它了。为了思考的简单,我们可以先考虑删除一个元素的算法:
template
struct types_erase
, 0, 1> { using type = types
; };
当数到需要删除的位置时,N一定是等于0的。这个时候若count为1,那么只需要去掉开头的T1就可以了。那么连续删除count个元素就可以这样写:
template
struct types_erase
, 0, C> : check_is_count_valid
, C> { using type = typename types_erase
, 0, C - 1>::type; };
当count不为1时,删除开头的T1,将count减1后继续向下递归。当count为1后,将匹配到前一个模板。由于这里的count可能超出types的界限,因此需要用check_is_count_valid来检查count的有效性。
现在,我们回过头来检查一下,模板