Contents

前阵子又去翻了翻《C++设计新思维》,这本书对我依然很受用,尤其是关于利用TMP技术做一些编译期工作的部分,很涨姿势。

因为已经发展了很多年,所以现在来看早就不是什么新鲜事物了,我也没啥新观点,纯粹写下个人体会吧。

看了其中一些编译期小工具的实现,感觉能直接帮助到自己目前的开发的还是只有很少一部分吧,但编程的思路的确拓宽了不少,也许未来在一些特定情况下能够利用这些技术漂亮的解决一些问题。

大多数工具要使用的话都得一大批工具配套的用,单单拿出其中一两件,无法组合起来用就难发挥其威力了。其中不少组件的实现乍一看都很神奇,仔细去看的时候又觉背后蕴含深入而复杂的思考…

总之自己想要随随便便做出一个所谓工业强度的桃木剑出来那是有些天方夜谭的。

我觉得之所以国内boost用的不多,国内C++开发人员平均素质偏低是一方面,觉得复杂难以驾驭是一方面,还有一个方面就是有时候我们项目只想用到里面一两个小组件,要为此引入一个这么重型的库进来,多少还是有点心理负担,多数情况下也就自己简单写下了。

话说回来,经过一段时间的学习,我觉得实现一些编译期计算的过程和我之前接触过一小阵子的Erlang的风格(或者说函数式语言的风格),还是有相当的类似之处的。

MPL一开始接触的时候也会觉得别扭,没实际用处,但是经过一段时间的适应,了解它的风格和能力之后,解决一些实际问题时自然就会联想到能否把一部分计算转移到编译期去。

先上个最简单的模板计算的栗子:

template <int N> struct Fib { enum { res = Fib<N-1>::res + Fib<N-2>::res }; };
template < > struct Fib<1> { enum { res = 1 }; };
template < > struct Fib<2> { enum { res = 1 }; };

之后通过Fib<N>::res就能获得相应位置的斐波那契数列的值,优化后完全不占用运行期时间。

再来看另一类非数值计算的典型栗子:

// TypeList
struct nil;

template <typename H, typename T>
struct TypeList
{
	typedef typename H head;
	typedef typename T tail;
};

template <typename H>
struct TypeList<H, nil>
{
	typedef H head;
	typedef nil tail;
};

// generate typelist
#define TYPELIST_1(type) TypeList<type, nil>
#define TYPELIST_2(type1, type2) TypeList<type1, TYPELIST_1(type2)>
#define TYPELIST_3(type1, type2, type3) TypeList<type1, TYPELIST_2(type2, type3)>
// ...create more...

// get type in typelist by idx
template <typename TList, int N> struct TypeAt
{
	typedef typename TypeAt<typename TList::tail, N-1>::res res;
};
template <typename TList> struct TypeAt<TList, 0>
{
	typedef typename TList::head res;
};
template <int N> struct TypeAt<nil, N> { typedef nil res; };
template < > struct TypeAt<nil, 0> { typedef nil res; };

// use case
typedef TYPELIST_3(int, double, char) tlist_t;
cout << typeid(tlist_t).name() << endl;
cout << typeid(TypeAt<tlist_t, 2>::res).name() << endl;

TypeList的实现是利用模板动态生成类的功能来针对类型进行编程,而TypeAt工具则是结合了数值计算和状态计算。

如果对一些函数式语言有接触,仔细观察这些代码会发现还是有比较明显的函数式编程风格的痕迹。比如计算规则高度抽象化,没有分支和循环一切都是递归计算,中间状态不可改(无副作用),需要修改就利用旧数据生成新的数据。

虽然理解起来有些别扭,但这里的struct实际上在我看来,是承担了函数定义的工作,而其中enum成员定义、typedef则提供了定义表达式和保存状态的功能,而模板参数,则是作为函数的入参来看待。

顺着这个思路,template早就已经不是像传统GP那样作为类型泛化的方法来用了,而是任何你想得到的计算实际上都利用这套工具(参数、函数、表达式、退出条件)来完成。

模板类的特化功能为“函数”递归创造了条件,意味着可以针对一个“函数”的某个“参数”针对的设置一个非递归的表达式,那样递归就可以在这个地方停止了。

实现模板状态计算的风格是和不管是过程式的C还是OO的C++都截然不同的,和一般而言函数式语言的feature一一比较,比如说:

无副作用,First-class function,类型推导,参数模式匹配,惰性求值,curry,部分应用,lambda 表达式,高阶函数,列表处理(取自CU)

这些东西大多也都能在C++ template中找到对应,最缺的大概是数据结构,不过理论上,就像实现一个TypeList那样,也是可以自己实现出来的,然后我们可以再在其上实现一些列的数据结构操作,然后…

就可以和编译器快乐的玩耍了!

Contents