下面是一些简单的测试:
Tuple<int, double, std::string> t{1, 1.2, "hello"}; std::cout << t.get<std::string>() << std::endl; t.get<std::string>() = "Hello, c++!"; std::cout << t.get<2>() << std::endl; std::cout << t.get<1>() << std::endl; std::cout << t.get<0>() << std::endl; // Output: // hello // Hello, c++! // 1.2 // 1 简化工厂模式假设我们有一个WidgetFactory,用来创建不同风格的Widgets,Widgets的种类有很多,例如Button,Label等:
class WidgetFactory { public: virtual CreateButton() = 0; virtual CreateLabel() = 0; virtual CreateToolBar() = 0; }; // 风格1 class KDEFactory: public WidgetFactory { public: CreateButton() override; CreateLabel() override; CreateToolBar() override; }; // 风格2 class GnomeFactory: public WidgetFactory { public: CreateButton() override; CreateLabel() override; CreateToolBar() override; }; // 使用 WidgetFactory* factory = new KDEFactory; factory->CreateButton(); // KDE button delete factory; factory = new GnomeFactory; factory->CreateButton(); // Gnome button这种实现有两个问题,一是如果增加/改变/减少产品,那么需要改动大量的代码,容易出错;二是创建不同种类的widget的代码通常是较为相似的,所以我们在这里需要不断复读自己,这通常是bug的根源之一。
较为理想的形式是什么呢?如果widget构造过程相同,只是参数上有差别,你可能已经想到了,我们有变长模板和完美转发:
class WidgetFactory { public: template <typename T, typename... Args> auto Create(Args&&... args) { return new T(std::forward<Args>(args)...); } };这样我们可以通过Create<KDEButton>(...)来创建不同的对象了,然而这已经不是一个工厂了,我们创建工厂的目的之一就是为了限制产品的种类,现在我们反而把限制解除了!
那么这么解决呢?答案还是TypeList,通过TypeList限制产品的种类:
template <typename... Widgets> class WidgetFactory { // 我们不需要重复的类型 using WidgetList = NoDuplicates<TypeList<Widgets...>>::result_type; public: template <typename T, typename... Args> auto Create(Args&&... args) { static_assert(IndexOf<WidgetList, T>::value != -1, "unknow type"); return new T(std::forward<Args>(args)...); } }; using KDEFactory = WidgetFactory<KDEButton, KDEWindow, KDELabel, KDEToolBar>; using GnomeFactory = WidgetFactory<GnomeLabel, GnomeButton>;现在如果我们想增加或改变某一个工厂的产品,只需要修改有限数量的代码即可,而且我们在限制了产品种类的同时将重复的代码进行了抽象集中。同时,类型检查都是编译期处理的,无需任何的运行时代价!
当然,这样简化的坏处是灵活性的降低,因为不同工厂现在实质是不同的不相关类型,不可能通过Base*或Base&关联起来,不过对于接口相同但是类型相同的对象,我们还是可以依赖模板实现静态分派,这只是设计上的取舍而已。
总结这篇文章只是对模板元编程的入门级探讨,旨在介绍如果使用现代c++简化元编程和泛型编程任务。
本文虽然不能带你入门元编程,但是可以让你对元编程的概念有一个整体的概览,对深入的学习是有帮助的。