模板元编程
什么是模板元
模板元编程是一种编译期计算的编程方法。如果你学过任意一门函数式的编程语言,那么你对模板元编程的理解一定是非常容易的。
模板元的编程约定
- 元函数
我们约定这样的一个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等等特性是极大地方便模板元编程的编写的,一些简单的模板元编程甚至可以用模板函数来实现,而不是模板类。当然了,对于复杂的模板元编程还是需要模板类的,因为偏特化的强大模式匹配功能是模板函数所不能替代的,除非在模板函数的参数列表中用各种标签,然而这得不偿失,很不优雅。