模板元编程

作者: 梅花怒 | 来源:发表于2018-11-26 11:53 被阅读6次

    什么是模板元

    模板元编程是一种编译期计算的编程方法。如果你学过任意一门函数式的编程语言,那么你对模板元编程的理解一定是非常容易的。


    模板元的编程约定

    • 元函数
      我们约定这样的一个struct称为一个元函数:
    template <template_args>
    struct func
    {
        do_something;
        using type = return_type;
    };
    

    对于一个元函数func,其template_args是func的参数,type是func的返回值。当然了,元函数的参数可以为空,即func是一个普通的struct而不是一个模板类,这也是元函数。

    • 元数据
      元数据是在编译期可以进行计算的数据。一般可以认为元数据有两种,即:整数值和类型。我们约定,一般情况,不要使用整数值作为元数据。因为其一,返回值永远是一个类型,这样显得对称;其二,类型永远比值好操控得多。当然,这不是绝对的规则,不要教条,例如我之前文章中写的If函数。那么,如果你想用整数值来计算,那么你可以使用整数值的外覆类:
    template <int N>
    struct Int
    {
        using type = Int<N>;
        static const int value = N;
    };
    
    • 元函数类
      元函数类是对元函数的一种包裹。元函数存在的目的即是为了更加方便地传递元函数(作为其他元函数的参数)。我们规定这样的一个非模板类为元函数类:
    struct func_class
    {
        template <template_args>
        struct apply
        {
            do_something;
            using type = return_type;
        };
    };
    

    我们约定,元函数类中包裹的元函数叫做apply。

    • 占位符表达式
      占位符表达式是这样的一种表达式:plus3<arg<1>, Int<4>, arg<2>>。其中arg<1>和arg<2>是占位符,并不是真正的plus3所期望的接受的参数。

    • Lambda表达式
      Lambda表达式是两者之一:占位符表达式、元函数类。

    • Lambda元函数
      Lambda元函数可以接受Lambda表达式,并且把Lambda表达式转化成元函数类。像这样:Lambda<T>::type。也就是说,Lambda元函数接受占位符表达式就把它转化成元函数类,接受元函数类就返回它自身。那么Lambda元函数把占位符表达式转化成什么样的元函数类呢?很简单,就比如上述说的plus3<arg<1>, Int<4>, arg<2>>这个占位符表达式。Lambda元函数把占位符表达式转化为一个元函数类,这个元函数类中的apply函数接受两个参数T1和T2(占位符所占的plus3的参数),返回值则是plus3<T1, Int<4>, T2>::type

    • Apply元函数
      Apply元函数是这样的元函数:Apply<T1, T2>::type。其中第一个参数是一个Lambda表达式,而第二个参数是传递给这个Lambda表达式的实参。最后求值。很显然,Apply元函数的实现的第一步一定是调用Lambda元函数操作第一个参数(Lambda表达式),返回一个元函数之后,再调用元函数的apply函数。

    • 惰性求值
      显然,我们用func<args>::type来调用一个元函数,得到返回值。我们::type的时机是可以选择的,这就给了我们可以惰性求值的机会。例如,我们调用我之前写过的If函数的时候,我们可以If<Flag, func1<T>, func2<T>>::type::type而不是If<Flag, func1<T>::type, func2<T>::type>::type


    如何编写模板元

    记住这几个Tips:

    1.对于临时变量,我们可以用类型存储。这也是为什么我们建议用Int外覆类而不是int类型值的原因之一。

    2.对于循环,循环对于一门语言来说其实是不必要的,可以用循环实现的一定可以用递归实现。所以慢慢习惯递归的写法,就会很自如地编写模板元程序了。

    3.对于数据结构的处理,千万别妄想自己可以in-place的处理数据结构。当对数据进行修改之后,要想着构造并返回新的数据结构。

    4.对于数据结构的实现,如果你熟悉Lisp的话,那么用模板元编程很容易去实现Cons,而且模板元编程还可以实现比Cons更方便的数据结构。如果你熟悉Haskell的话,其实Haskell的[a]就是Cons的语法糖。

    模板元编程的具体使用方法可以参见我的文章,希望对你有启发。


    Modern C++下的模板元编程

    首先,可变模板参数、constexpr、if constexpr等等特性是极大地方便模板元编程的编写的,一些简单的模板元编程甚至可以用模板函数来实现,而不是模板类。当然了,对于复杂的模板元编程还是需要模板类的,因为偏特化的强大模式匹配功能是模板函数所不能替代的,除非在模板函数的参数列表中用各种标签,然而这得不偿失,很不优雅。

    相关文章

      网友评论

        本文标题:模板元编程

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