美文网首页C++ Templates
【C++ Templates(22)】Typelist

【C++ Templates(22)】Typelist

作者: downdemo | 来源:发表于2018-06-29 14:51 被阅读49次
  • 高效编程一般需要使用各种数据结构,元编程也不例外。对于类型元编程,核心数据结构就是typelist,顾名思义,一个包含类型的列表。模板元编程能操作这些类型列表以生成部分可执行程序。大多数调用typelist的操作使用了模板元编程,所以此前务必先熟悉元编程

Typelist解析

  • typelist提供和一个列表相关的操作:迭代列表中的元素,添加或移除元素。然而typelist不同于大多数运行时数据结构,如std::list,typelist不允许转变。比如,添加一个元素到std::list改变链表本身,且改变能被其他有访问权的任何部分观测到。添加一个元素到typelist则不会改变原本的typelist,而是创建一个新的typelist
  • 一个typelist通常实现为一个类模板特化,它对模板实参中typelist的内容(即包含的类型及其顺序)进行编码。一个直接的实现就是对参数包抓红的元素编码
// typelist/typelist.hpp
template<typename... Elements>
class Typelist
{
};
  • Typelist(上面这个类)的元素直接写为模板实参,一个空的typelist写为Typelist<>,只包含int的typelist则写为Typelist<int>,以此类推。下面是包含所有有符号整型的typelist
using SignedIntegralTypes =
    Typelist<signed char, short, int, long, long long>;
  • 操作这个typelist通常需要将它拆分开,一般分离第一个元素在一个列表(头部),剩下的在另一个列表(尾部)。比如下面的Front元函数从typelist解压第一个元素
// typelist/typelistfront.hpp

template<typename List>
class FrontT;

template<typename Head, typename... Tail>
class FrontT<Typelist<Head, Tail...>>
{
public:
    using Type = Head;
};

template<typename List>
using Front = typename FrontT<List>::Type;
  • 因此FrontT<SignedIntegralTypes>::Type将生成signed char。类似地,下面的PopFront元函数从typelist移除第一个元素,它将typelist元素分为头部和尾部,然后从尾部元素构建一个新的Typelist特化
// typelist/typelistpopfront.hpp

template<typename List>
class PopFrontT;

template<typename Head, typename... Tail>
class PopFrontT<Typelist<Head, Tail...>> {
public:
    using Type = Typelist<Tail...>;
};

template<typename List>
using PopFront = typename PopFrontT<List>::Type;
  • PopFront<SignedIntegralTypes>生成Typelist<short, int, long, long long>
  • 也可以通过捕获一个模板参数包中所有现有的元素,插入元素到typelist之前,然后创建一个新的包含所有那些元素的Typelist特化
// typelist/typelistpushfront.hpp

template<typename List, typename NewElement>
class PushFrontT;

template<typename... Elements, typename NewElement>
class PushFrontT<Typelist<Elements...>, NewElement> {
public:
    using Type = Typelist<NewElement, Elements...>;
};

template<typename List, typename NewElement>
using PushFront = typename PushFrontT<List, NewElement>::Type;
  • PushFront<SignedIntegralTypes, bool>生成Typelist<bool, signed char, short, int, long, long long>

Typelist算法

  • 基本的typelist操作Front、PopFront和PushFront能组合起来创建更有趣的typelist操作,比如可以通过应用PushFront到PopFront的结果,替代typelist的第一个元素
using Type = PushFront<PopFront<SignedIntegralTypes>, bool>;
// equivalent to Typelist<bool, short, int, long, long long>
  • 更远一些,可以实现模板元函数操作在typelist上的算法,如搜索、转换、翻转

索引

  • 一个typelist上最基本的操作之一是从列表解压一个特定的元素,这里归纳这个操作以解压第N个元素,比如解压给定typelist的在索引2位置的类型
using TL = NthElement<Typelist<short, int, long>, 2>;
  • 这里使得TL相当于long的别名,NthElement操作使用递归元程序实现,它将遍历typelist直到找到需要的元素
// typelist/nthelement.hpp

// recursive case:
template<typename List, unsigned N>
class NthElementT : public NthElementT<PopFront<List>, N-1>
{
};

// basis case:
template<typename List>
class NthElementT<List, 0> : public FrontT<List>
{
};

template<typename List, unsigned N>
using NthElement = typename NthElementT<List, N>::Type;
  • 首先考虑N为0的基本情况,这个特化通过提供在列表前的元素终止递归,它通过公有继承自FrontT<List>实现这点
  • 递归情况也是模板的基本定义,它遍历typelist,由于局部特化保证N>0,递归情况移除前面的元素并请求剩余列表的第(N-1)个元素。在我们的例子中
NthElementT<Typelist<short, int, long>, 2>
// 继承自
NthElementT<Typelist<int, long>, 1>
// 继承自
NthElementT<Typelist<long>, 0>

找到最佳匹配

  • 有许多查找数据的typelist算法,比如找到typelist中最大的类型(比如为了给列表中的任何类型分配足够的存储空间),这可以用一个递归模板元程序实现
// typelist/largesttype.hpp

template<typename List>
class LargestTypeT;

// recursive case:
template<typename List>
class LargestTypeT
{
private:
    using First = Front<List>;
    using Rest = typename LargestTypeT<PopFront<List>>::Type;
public:
    using Type = IfThenElse<(sizeof(First) >= sizeof(Rest)), First, Rest>;
};

// basis case:
template<>
class LargestTypeT<Typelist<>>
{
public:
    using Type = char;
};

template<typename List>
using LargestType = typename LargestTypeT<List>::Type;
  • LargestType算法将返回typelist中第一个最大的类型,比如给出Typelist<bool, int, long, short>,这个算法将返回第一个和long一样大的类型,可能是int或long,取决于平台
  • LargestTypeT的基本模板兼职算法的递归的情况,它采用通用的first/rest idiom,有三个步骤。第一步,计算只基于第一个元素的部分结果,在这个例子中,它是列表的前一个元素,并将其放在First中。接着,它递归计算列表中剩余元素的结果,并把结果放在Rest中。比如在Typelist<bool, int,
    long, short>的第一步递归中,First是bool,Rest是将这个算法用于Typelist<int, long, short>的结果。第三步结合First和Rest结果来产生答案。这里IfThenElse选择列表中的第一个元素(First)或迄今为止最好的候选(Rest)中更大的那个,并返回优胜者。>=打破支持更早出现在列表中的元素的关系
  • 当列表为空时,递归终止。默认地,使用char作为一个哨兵类型来初始化算法,因为每个类型和char一样大
  • 注意基本情况显式指出空的typelist为Typelist<>,这有点不幸,因为它排除了使用其他类型的typelist。为了解决这个问题,引入一个IsEmpty元函数来判断给出的typelist是否没有元素
// typelist/typelistisempty.hpp

template<typename List>
class IsEmpty
{
public:
    static constexpr bool value = false;
};

template<>
class IsEmpty<Typelist<>> {
public:
    static constexpr bool value = true;
};
  • 使用IsEmpty,可以实现LargestType以使它对于任何实现Front、PopFront和IsEmpty的typelist同样适用
// typelist/genericlargesttype.hpp

template<typename List, bool Empty = IsEmpty<List>::value>
class LargestTypeT;

// recursive case:
template<typename List>
class LargestTypeT<List, false>
{
private:
    using Contender = Front<List>;
    using Best = typename LargestTypeT<PopFront<List>>::Type;
public:
    using Type = IfThenElse<(sizeof(Contender) >= sizeof(Best)),
        Contender, Best>;
};

// basis case:
template<typename List>
class LargestTypeT<List, true>
{
public:
    using Type = char;
};

template<typename List>
using LargestType = typename LargestTypeT<List>::Type;

添加Typelist

  • PushFront原始操作允许添加新元素到typelist之前,生成一个新的typelist,假如想添加到列表末尾,只要小小改动PushFront就能得到PushBack
// typelist/typelistpushback.hpp

template<typename List, typename NewElement>
class PushBackT;

template<typename... Elements, typename NewElement>
class PushBackT<Typelist<Elements...>, NewElement>
{
public:
    using Type = Typelist<Elements..., NewElement>;
};

template<typename List, typename NewElement>
using PushBack = typename PushBackT<List, NewElement>::Type;
  • 然而,和LargestType算法一样,可以为PushBack实现一个通用的算法,只要使用原始操作Front、PushFront、PopFront和IsEmpty
// typelist/genericpushback.hpp

template<typename List, typename NewElement, bool =
IsEmpty<List>::value>
class PushBackRecT;

// recursive case:
template<typename List, typename NewElement>
class PushBackRecT<List, NewElement, false>
{
    using Head = Front<List>;
    using Tail = PopFront<List>;
    using NewTail = typename PushBackRecT<Tail, NewElement>::Type;
public:
    using Type = PushFront<Head, NewTail>;
};

// basis case:
template<typename List, typename NewElement>
class PushBackRecT<List, NewElement, true>
{
public:
    using Type = PushFront<List, NewElement>;
};

// generic push-back operation:
template<typename List, typename NewElement>
class PushBackT : public PushBackRecT<List, NewElement> { };

template<typename List, typename NewElement>
using PushBack = typename PushBackT<List, NewElement>::Type;
  • PushBackRecT模板管理递归,在基本情况中,使用PushFront添加NewElement到空列表,因为在空列表中PushFront等价于PushBack。递归情况有趣得多:它分离这个列表为第一个元素(Head)和一个包含剩余元素的typelist(Tail),新元素再添加到Tail,递归地生成NewTail,然后再使用PushFront添加Head到NewTail之前来构建最后的列表
  • 来对一个简单的例子打开递归
PushBackRecT<Typelist<short, int>, long>
  • 在我们的最外层步骤中,Head是short,Tail是Typelist<int>,递归为
PushBackRecT<Typelist<int>, long>
  • Head是int,Tail是Typelist<>,再次递归计算
PushBackRecT<Typelist<>, long>
  • 触发基本情况并返回PushFront<Typelist<>, long>,它本身计算为Typelist<long>,随后递归展开,把之前的Head压入列表前
PushFront<int, Typelist<long>>
  • 这生成Typelist<int, long>,递归再次展开,把最外层的Head(short)压入列表
PushFront<short, Typelist<int, long>>
  • 这产生最终结果
Typelist<short, int, long>
  • 通用的PushBackRecT实现能用于任何类型的typelist,就像之前的算法一样,它需要一个线性数量的模板实例化来计算,因为对于长度为N的typelist,将有N+1个PushBackRect和PushFrontT的实例化,以及N个FrontT和PopFrontT的实例化。计算这些模板实例化的数量能粗略估计编译一个特定元程序的时间,因为模板实例化本身相当于一个编译器的调用进程
  • 对于巨大的模板元程序,编译时间是一个问题,因此尝试减少通过执行这些算法产生的模板实例化数量是合理的。事实上,在第一个PushBack的实现中,采用了一个Typelist的局部特化,只需要一个常数数量的模板实例化,这使其在编译期比泛型版本更高效。此外,由于它被描述为一个PushBackT的局部特化,当在一个Typelist实例上执行PushBack时,这个高效的实现将自动被选择,将算法特化的概念引入模板元编程

翻转Typelist

  • 当typelist的元素有一定顺序时,用一些算法可以很方便地翻转元素顺序。比如SignedIntegralTypes根据升序排序,然而翻转为降序来产生Typelist<long
    long, long, int, short, signed char>可能更有用,Reverse算法可实现这个元函数
// typelist/typelistreverse.hpp

template<typename List, bool Empty = IsEmpty<List>::value>
class ReverseT;

template<typename List>
using Reverse = typename ReverseT<List>::Type;

// recursive case:
template<typename List>
class ReverseT<List, false>
: public PushBackT<Reverse<PopFront<List>>, Front<List>> { };

// basis case:
template<typename List>
class ReverseT<List, true>
{
public:
    using Type = List;
};
  • 元函数的递归基本情况是一个空的typelist上的识别函数,递归情况把列表分离为首元素和剩余元素。比如,如果给出Typelist<short, int, long>,递归先把short分离出来,然后递归地翻转剩余的元素(生成Typelist<long,
    int>),最后用PushBackT把首元素添加到翻转后的列表(生成Typelist<long, int, short>)
  • Reverse算法使得实现一个移除尾元素的PopBackT操作可行
// typelist/typelistpopback.hpp

template<typename List>
class PopBackT {
public:
    using Type = Reverse<PopFront<Reverse<List>>>;
};

template<typename List>
using PopBack = typename PopBackT<List>::Type;
  • 这个算法翻转列表,使用PopFront移除首元素,然后再次翻转列表

转换Typelist

  • 有时可能想转换列表中的所有类型,比如使用AddConst元函数把每个类型转换为const版本
// typelist/addconst.hpp

template<typename T>
struct AddConstT
{
    using Type = T const;
};

template<typename T>
using AddConst = typename AddConstT<T>::Type;
  • 接着,实现一个Transform算法,使用一个typelist和一个元函数(Transform<SignedIntegralTypes, AddConstT>),生成另一个typelist
// typelist/transform.hpp

template<typename List, template<typename T> class MetaFun,
bool Empty = IsEmpty<List>::value>
class TransformT;

// recursive case:
template<typename List, template<typename T> class MetaFun>
class TransformT<List, MetaFun, false>
: public PushFrontT<typename TransformT<PopFront<List>,
MetaFun>::Type,
typename MetaFun<Front<List>>::Type>
{
};

// basis case:
template<typename List, template<typename T> class MetaFun>
class TransformT<List, MetaFun, true>
{
public:
    using Type = List;
};

template<typename List, template<typename T> class MetaFun>
using Transform = typename TransformT<List, MetaFun>::Type;

累加Typelist

  • Transform常和Accumulate算法一起使用。Accumulate算法使用一个带有元素T1,T2,...,TN的typelist T,一个初始化类型I,一个接受两个类型返回一个类型的元函数F。Accumulate返回F (F (F (...F (I, T1), T2), ..., TN−1), TN )
  • 根据typelist,选用的F,初始化类型,可以使用Accumulate生成许多不同的输出。比如如果F选择两个类型中最大的那个,Accumulate的行为就像LargestType算法,如果F接受一个typelist和一个类型并把类型放到typelist尾部,Accumulate的行为就像Reverse算法
// typelist/accumulate.hpp

template<typename List,
    template<typename X, typename Y> class F,
    typename I,
    bool = IsEmpty<List>::value>
class AccumulateT;

// recursive case:
template<typename List,
    template<typename X, typename Y> class F,
    typename I>
class AccumulateT<List, F, I, false>
: public AccumulateT<PopFront<List>, F,
    typename F<I, Front<List>>::Type>
{
};

// basis case:
template<typename List,
    template<typename X, typename Y> class F,
    typename I>
class AccumulateT<List, F, I, true>
{
public:
    using Type = I;
};

template<typename List,
    template<typename X, typename Y> class F,
    typename I>
using Accumulate = typename AccumulateT<List, F, I>::Type;
  • 使用Accumulate,可以使用PushFrontT作为元函数F,空的typelist(TypeList<T>)作为初始类型I,来翻转typelist
using Result = Accumulate<SignedIntegralTypes, PushFrontT, Typelist<>>;
// produces TypeList<long long, long, int, short, signed char>
  • 实现一个Accumulator-based版本的LargestType,LargestTypeAcc需要一些努力,因为需要生成一个返回两个类型中较大者的元函数
// typelist/largesttypeacc0.hpp

template<typename T, typename U>
class LargerTypeT
: public IfThenElseT<sizeof(T) >= sizeof(U), T, U>
{
};

template<typename Typelist>
class LargestTypeAccT
: public AccumulateT<PopFront<Typelist>, LargerTypeT,
Front<Typelist>>
{
};

template<typename Typelist>
using LargestTypeAcc = typename LargestTypeAccT<Typelist>::Type;
  • 注意这个LargestType的构建需要一个非空的typelist,因为它提供typelist的首元素作为初始类型。可以显式处理这个空列表情况,通过返回一些哨兵类型(char或void)或通过使得算法本身SFINAE-friendly
// typelist/largesttypeacc.hpp

template<typename T, typename U>
class LargerTypeT
: public IfThenElseT<sizeof(T) >= sizeof(U), T, U>
{
};

template<typename Typelist, bool = IsEmpty<Typelist>::value>
class LargestTypeAccT;
template<typename Typelist>
class LargestTypeAccT<Typelist, false>
: public AccumulateT<PopFront<Typelist>, LargerTypeT,
Front<Typelist>>
{
};

template<typename Typelist>
class LargestTypeAccT<Typelist, true>
{
};

template<typename Typelist>
using LargestTypeAcc = typename LargestTypeAccT<Typelist>::Type;

插入排序

  • 最终的typelist算法是实现插入排序,和其他算法一样,递归把列表分解首元素(头部)和剩余元素(尾部),尾部随后递归地排序,头部再插入排序后的列表的正确的位置
// typelist/insertionsort.hpp

template<typename List,
    template<typename T, typename U> class Compare,
    bool = IsEmpty<List>::value>
class InsertionSortT;

template<typename List,
    template<typename T, typename U> class Compare>
using InsertionSort = typename InsertionSortT<List, Compare>::Type;

// recursive case (insert first element into sorted list):
template<typename List,
    template<typename T, typename U> class Compare>
class InsertionSortT<List, Compare, false>
: public InsertSortedT<InsertionSort<PopFront<List>, Compare>,
    Front<List>, Compare>
{
};

// basis case (an empty list is sorted):
template<typename List,
    template<typename T, typename U> class Compare>
class InsertionSortT<List, Compare, true>
{
public:
    using Type = List;
};
  • 插入排序的核心是InsertSortedT元函数,它把一个值插入一个已排序的列表中
// typelist/insertsorted.hpp

#include "identity.hpp"
template<typename List, typename Element,
    template<typename T, typename U> class Compare,
    bool = IsEmpty<List>::value>
class InsertSortedT;

// recursive case:
template<typename List, typename Element,
    template<typename T, typename U> class Compare>
class InsertSortedT<List, Element, Compare, false>
{
    // compute the tail of the resulting list:
    using NewTail =
        typename IfThenElse<Compare<Element, Front<List>>::value,
        IdentityT<List>,
        InsertSortedT<PopFront<List>, Element, Compare>
        >::Type;

    // compute the head of the resulting list:
    using NewHead = IfThenElse<Compare<Element, Front<List>>::value,
        Element,
        Front<List>>;
public:
    using Type = PushFront<NewTail, NewHead>;
};

// basis case:
template<typename List, typename Element,
    template<typename T, typename U> class Compare>
class InsertSortedT<List, Element, Compare, true>
: public PushFrontT<List, Element>
{
};

template<typename List, typename Element,
    template<typename T, typename U> class Compare>
using InsertSorted = typename InsertSortedT<List, Element, Compare>::Type;
  • 这个实现包含一个禁止初始化不使用的类型的编译期优化,下面的实现技术上也是正确的
template<typename List, typename Element,
    template<typename T, typename U> class Compare>
class InsertSortedT<List, Element, Compare, false>
: public IfThenElseT<Compare<Element, Front<List>>::value,
    PushFront<List, Element>,
    PushFront<InsertSorted<PopFront<List>,
        Element, Compare>,
    Front<List>>>
{
};
  • 然而这样一个递归情况的构建是没必要的,因为它计算了IfThenElseT所有分支的模板实参,这里then分支的PushFront是低开销的,但else分支的InsertSorted则不是
  • 下面演示了对一个类型的列表基于他们的大小使用插入排序
// typelist/insertionsorttest.hpp

template<typename T, typename U>
struct SmallerThanT {
    static constexpr bool value = sizeof(T) < sizeof(U);
};

void testInsertionSort()
{
    using Types = Typelist<int, char, short, double>;
    using ST = InsertionSort<Types, SmallerThanT>;
    std::cout << std::is_same<ST,Typelist<char, short, int, double>>::value
        << '\n';
}

非类型Typelist

  • 在一些情况下,typelist也能用于编译期值的序列,一个简单的生成编译期值的typelist的方法是定义一个CRValue类模板,它表示一个typelist中特定类型的值
// typelist/ctvalue.hpp
template<typename T, T Value>
struct CTValue
{
    static constexpr T value = Value;
};
  • 使用CTValue模板,现在可以表达一个包含质数整型值的typelist
using Primes = Typelist<CTValue<int, 2>, CTValue<int, 3>,
    CTValue<int, 5>, CTValue<int, 7>,
    CTValue<int, 11>>;
  • 使用这个表示,可以在一个值typelist上进行数值计算。一个MultiplyT模板接受两个同类型的编译期值并生成一个新的同类型编译期值,它为接受的两个值的乘积
// typelist/multiply.hpp

template<typename T, typename U>
struct MultiplyT;

template<typename T, T Value1, T Value2>
struct MultiplyT<CTValue<T, Value1>, CTValue<T, Value2>>
{
public:
    using Type = CTValue<T, Value1 * Value2>;
};

template<typename T, typename U>
using Multiply = typename MultiplyT<T, U>::Type;

*通过使用MultiplyT,下面的表达式产生所有质数的乘积结果

Accumulate<Primes, MultiplyT, CTValue<int, 1>>::value
  • 不幸的是,这个对Typelist和CTValue的使用十分冗长,尤其是对于所有值类型相同的情况。通过引入一个别名模板CTTypelist能优化这种情况,它提供一个描述为CTValues的Typelist的统一的值列表
// typelist/cttypelist.hpp
template<typename T, T... Values>
using CTTypelist = Typelist<CTValue<T, Values>...>;
  • 现在使用CTTypelist可以写出一个更简单的Primes的等价定义
using Primes = CTTypelist<int, 2, 3, 5, 7, 11>;
  • 这个方法唯一的缺点是,别名模板只是别名,出现错误信息还是打印下层的CTValueType的Typelist,而这可能造成更冗长的问题。为了解决这个问题,可以创建一个全新的typelist类Valuelist来直接存储值
// typelist/valuelist.hpp

template<typename T, T... Values>
struct Valuelist {
};

template<typename T, T... Values>
struct IsEmpty<Valuelist<T, Values...>> {
    static constexpr bool value = sizeof...(Values) == 0;
};

template<typename T, T Head, T... Tail>
struct FrontT<Valuelist<T, Head, Tail...>> {
    using Type = CTValue<T, Head>;
    static constexpr T value = Head;
};

template<typename T, T Head, T... Tail>
    struct PopFrontT<Valuelist<T, Head, Tail...>> {
    using Type = Valuelist<T, Tail...>;
};

template<typename T, T... Values, T New>
struct PushFrontT<Valuelist<T, Values...>, CTValue<T, New>> {
    using Type = Valuelist<T, New, Values...>;
};

template<typename T, T... Values, T New>
struct PushBackT<Valuelist<T, Values...>, CTValue<T, New>> {
    using Type = Valuelist<T, Values..., New>;
};
  • 通过提供IsEmpty,FrontT,PopFrontT和PushFrontT,使得Valuelist成为能更合适地用于算法的typelist。PushBackT提供为一个减少编译期操作开销的算法特化。比如Valuelist用于之前的定义InsertionSort
// typelist/valuelisttest.hpp

template<typename T, typename U>
struct GreaterThanT;

template<typename T, T First, T Second>
struct GreaterThanT<CTValue<T, First>, CTValue<T, Second>> {
    static constexpr bool value = First > Second;
};

void valuelisttest()
{
    using Integers = Valuelist<int, 6, 2, 4, 9, 5, 2, 1, 7>;
    using SortedIntegers = InsertionSort<Integers, GreaterThanT>;
    static_assert(std::is_same_v<SortedIntegers,
    Valuelist<int, 9, 7, 6, 5, 4, 2, 2, 1>>, "insertion sort failed");
}
  • 注意可以提供使用字面值操作符初始化CTValue的能力,具体细节见Tuple Subscript(Tuple下标)
auto a = 42_c; // initializes a as CTValue<int,42>

可推断的非类型参数

  • C++17中,CTValue能通过使用单个可推断的非类型参数改进
// typelist/ctvalue17.hpp

template<auto Value>
struct CTValue
{
    static constexpr auto value = Value;
};
  • 这就省去了对每个CTValue的使用指定特定类型的需要
using Primes = Typelist<CTValue<2>, CTValue<3>, CTValue<5>,
    CTValue<7>, CTValue<11>>;
  • 这也能用于C++17的Valuelist,但结果不一定更好。一个带可推断类型的非类型参数包允许每个实参类型不同
template<auto... Values>
class Valuelist { };
int x;
using MyValueList = Valuelist<1,'a', true, &x>;
  • 但这样混杂的值列表可能是有用的,它不同于之前的Valuelist要求所有元素有相同类型

使用包扩展来优化算法

  • Transform算法就自然地使得本身使用包扩展,因为它对每个列表中的元素进行了相同的操作,这允许了一个对Typelist的Transform的算法特化
// typelist/variadictransform.hpp

template<typename... Elements, template<typename T> class MetaFun>
class TransformT<Typelist<Elements...>, MetaFun, false>
{
public:
    using Type = Typelist<typename MetaFun<Elements>::Type...>;
};
  • 这个实现捕获了typelist元素到参数包Elements中,随后采用了一个模式为typename MetaFun<Elements>::Type的包扩展以应用元函数到Elements中的每个类型,并从结果构建一个typelist。这种实现可以说更简单,因为它不需要递归,并且十分直接地使用语言特性。此外,它需要更少的模板实例化,因为只有一个Transform模板需要实例化。算法仍然要求线性数量的MeraFun实例化,但那些实例化对算法来说是基本的
  • 其他算法间接受益于使用包扩展。比如Reverse算法需要一个线性数量的PushBack的实例化,使用Typelist上的PushBack的包扩展构建,Reverse是线性的。而更通用的Reverse的递归实现本身是线性实例化的,这使得Reverse是二次的
  • 包扩展也可以用于选择给定索引列表中的元素以生成新的typelist。Selecty元函数使用一个typelist和一个包含typelist索引的Valuelist,生成一个新的typelist
// typelist/select.hpp

template<typename Types, typename Indices>
class SelectT;

template<typename Types, unsigned... Indices>
class SelectT<Types, Valuelist<unsigned, Indices...>>
{
public:
    using Type = Typelist<NthElement<Types, Indices>...>;
};

template<typename Types, typename Indices>
using Select = typename SelectT<Types, Indices>::Type;
  • 索引被捕获在参数包Indices中,它扩展为生成一个给定typelist索引的NthElement类型序列,并将结果捕获在一个新的Typelist中。下面使用Select翻转typelist
using SignedIntegralTypes =
    Typelist<signed char, short, int, long, long long>;

using ReversedSignedIntegralTypes =
    Select<SignedIntegralTypes, Valuelist<unsigned, 4, 3, 2, 1, 0>>;
    // produces Typelist<long long, long, int, short, signed char>
  • 一个包含另一个列表索引的非类型typelist通常称为索引表(index list或index sequence)

Cons-style Typelist

  • 在引入可变参数模板之前,typelist常用于使用一个递归的LISP的cons单元建模后的数据结构的构建。每个cons单元包含一个值(列表的头)和一个嵌套列表,后者可以是另一个cons单元或空列表nil。这个概念可以直接用C++来表达
// typelist/cons.hpp
class Nil { };
template<typename HeadT, typename TailT = Nil>
class Cons {
public:
    using Head = HeadT;
    using Tail = TailT;
};
  • 一个空的typelist写为Nil,一个包含单独的int元素的typelist写为Cons<int, Nil>,或Cons<int>,更长的列表需要嵌套
using TwoShort = Cons<short, Cons<unsigned short>>;
  • 任意长typelist能通过深度递归嵌套构造,尽管手写出这样的长列表十分笨拙
using SignedIntegralTypes = Cons<signed char, Cons<short, Cons<int,
    Cons<long, Cons<long long, Nil>>>>>;
  • 提取cons-style列表中第一个元素,直接指向列表的头
// typelist/consfront.hpp

template<typename List>
class FrontT {
public:
    using Type = typename List::Head;
};

template<typename List>
using Front = typename FrontT<List>::Type;
  • 添加一个元素到前面来包裹现有列表中的另一个Cons
// typelist/conspushfront.hpp

template<typename List, typename Element>
class PushFrontT {
public:
    using Type = Cons<Element, List>;
};

template<typename List, typename Element>
using PushFront = typename PushFrontT<List, Element>::Type;
  • 最后,从一个递归的typelist移除首个元素提取列表的尾部
// typelist/conspopfront.hpp

template<typename List>
class PopFrontT {
public:
    using Type = typename List::Tail;
};

template<typename List>
using PopFront = typename PopFrontT<List>::Type;
  • 一个对Nil的IsEmpty特化完成一系列核心的typelist操作
// typelist/consisempty.hpp
template<typename List>
struct IsEmpty {
    static constexpr bool value = false;
};

template<>
struct IsEmpty<Nil> {
    static constexpr bool value = true;
};
  • 通过这些typelist操作,现在可以对cons-style列表使用InsertionSort算法
// typelist/conslisttest.hpp
template<typename T, typename U>
struct SmallerThanT {
    static constexpr bool value = sizeof(T) < sizeof(U);
};

void conslisttest()
{
    using ConsList = Cons<int, Cons<char, Cons<short, Cons<double>>>>;
    using SortedTypes = InsertionSort<ConsList, SmallerThanT>;
    using Expected = Cons<char, Cons<short, Cons<int, Cons<double>>>>;
    std::cout << std::is_same<SortedTypes, Expected>::value << '\n';
}

相关文章

网友评论

    本文标题:【C++ Templates(22)】Typelist

    本文链接:https://www.haomeiwen.com/subject/mmjayftx.html