先是我们的玩具tuple,之所以说它简陋是因为我们只选择实现了get这一个接口,并且标准库的tuple并不是向我们这样实现的,因此这里的tuple只是一个演示用的玩具罢了。
首先是我们用来存储数据的节点:
接着我们实现Tuple:
我们的Tuple实现地简单暴力,通过private继承,我们就可以同时存储多种不同的数据,引用的时候只需要Data<type>.value_
,因此我们的第一个get很容易就实现了,只需要检查TypeList中是否存在对应类型即可。
但是标准库的get还有第二种形式:get<1>()
。对于第一种get,事实上我们不借助TypeList也能实现,但是对于第二种我们就不得不借助TypeList的力量了,因为我们除了利用元容器记录type的出现顺序之外别无办法(这也是为什么标准库不会这样实现tuple的原因之一)。因此我们利用TypeAt
元函数找到对应的类型后再获取它的值。
另外标准库不使用这种形式最重要的原因就是如果你在tuple里存储了2个以上相同type的数据,会报错,很容易想到是为什么。
所以类似的技术更适合用于variant
这样的对象,不过这里只是举例所以我们忽略了这些问题。
下面是一些简单的测试:
假设我们有一个WidgetFactory
,用来创建不同风格的Widgets,Widgets的种类有很多,例如Button,Label等:
这种实现有两个问题,一是如果增加/改变/减少产品,那么需要改动大量的代码,容易出错;二是创建不同种类的widget的代码通常是较为相似的,所以我们在这里需要不断复读自己,这通常是bug的根源之一。
较为理想的形式是什么呢?如果widget构造过程相同,只是参数上有差别,你可能已经想到了,我们有变长模板和完美转发:
这样我们可以通过Create<KDEButton>(...)
来创建不同的对象了,然而这已经不是一个工厂了,我们创建工厂的目的之一就是为了限制产品的种类,现在我们反而把限制解除了!
那么这么解决呢?答案还是TypeList,通过TypeList限制产品的种类:
现在如果我们想增加或改变某一个工厂的产品,只需要修改有限数量的代码即可,而且我们在限制了产品种类的同时将重复的代码进行了抽象集中。同时,类型检查都是编译期处理的,无需任何的运行时代价!
当然,这样简化的坏处是灵活性的降低,因为不同工厂现在实质是不同的不相关类型,不可能通过Base*
或Base&
关联起来,不过对于接口相同但是类型相同的对象,我们还是可以依赖模板实现静态分派,这只是设计上的取舍而已。
这篇文章只是对模板元编程的入门级探讨,旨在介绍如果使用现代c++简化元编程和泛型编程任务。
本文虽然不能带你入门元编程,但是可以让你对元编程的概念有一个整体的概览,对深入的学习是有帮助的。