现代C++与模板元编程(6)

首先我们递归调用MostDerived,结果保存为candidate,这是Base在去除Head之后的list中最深层次的派生类或是Base自己,然后我们判断Head是否是candidate的派生类,如果是就返回Head,否则返回candidate,这样就可以得到最末端的派生类类型了。

std::conditional_t则是c++11的type_traits提供的基础设施之一,通过bool值返回类型,有了它我们就可以省去自己实现Select的工夫了。

完成帮助元函数后就可以着手实现Derived2Front了:

template <typename TList> struct Derived2Front; template <> struct Derived2Front<TypeList<>> { using result_type = TypeList<>; }; template <typename Head, typename... Tails> struct Derived2Front<TypeList<Head, Tails...>> { private: using theMostDerived = typename MostDerived<TypeList<Tails...>, Head>::result_type; using List = typename Replace<TypeList<Tails...>, theMostDerived, Head>::result_type; public: using result_type = typename Append<theMostDerived, List>::result_type; };

算法步骤不复杂,先找到最末端的派生类,然后将去除头部的TypeList中与最末端派生类相同的元素替换为Head,最后我们把最末端的派生类添加在处理过的TypeList的最前面,就完成了派生类从末端移动到前端。

元函数实现总结

通过这些元函数的示例,我们可以看到现代c++对于元编程有了更多的内建支持,利用新的标准库和语言特性我们可以少写很多代码,也可以实现在c++11之前看似根本不可能的任务。

当然现代c++也带来了自己独有的问题,比如边长模板参数包无法直接迭代,这导致了我们大多数时间仍然需要依赖递归和偏特化这样的古典技法。

然而不可否认的是,随着语言的进化,c++进行元编程的难度在不断下降,元编程的能力和代码的表现力也越来越强了。

示例

我想通过两个示例来更好地展示TypeList和现代c++的威力。

第一个例子是个简陋的tuple类型,模仿了标准库。

第二个例子是工厂类,传统的工厂模式要么避免不了复杂的继承结构,要么避免不了大量的硬编码导致扩展困难,我们使用TypeList来解决这些问题。

自制tuple

首先是我们的玩具tuple,之所以说它简陋是因为我们只选择实现了get这一个接口,并且标准库的tuple并不是向我们这样实现的,因此这里的tuple只是一个演示用的玩具罢了。

首先是我们用来存储数据的节点:

template <typename T> struct Data { explicit Data(T&& v): value_(std::move(v)) {} T value_; };

接着我们实现Tuple:

template <typename... Args> class Tuple: private Data<Args>... { using TList = TypeList<Args...>; public: explicit Tuple(Args&&... args) : Data<Args>(std::forward<Args>(args))... {} template <typename Target> Target& get() { static_assert(IndexOf<TList, Target>::value != -1, "invalid type name"); return Data<Target>::value_; } template <std::size_t Index> auto& get() { static_assert(Index < Length<TList>::value, "index out of range"); return get<typename TypeAt<TList, Index>::type>(); } // const的重载 template <typename Target> const Target& get() const { static_assert(IndexOf<TList, Target>::value != -1, "invalid type name"); return Data<Target>::value_; } template <std::size_t Index> const auto& get() const { static_assert(Index < Length<TList>::value, "index out of range"); return get<typename TypeAt<TList, Index>::type>(); } }; // 空Tuple的特化 template <> class Tuple<> {};

我们的Tuple实现地简单暴力,通过private继承,我们就可以同时存储多种不同的数据,引用的时候只需要Data<type>.value_,因此我们的第一个get很容易就实现了,只需要检查TypeList中是否存在对应类型即可。

但是标准库的get还有第二种形式:get<1>()。对于第一种get,事实上我们不借助TypeList也能实现,但是对于第二种我们就不得不借助TypeList的力量了,因为我们除了利用元容器记录type的出现顺序之外别无办法(这也是为什么标准库不会这样实现tuple的原因之一)。因此我们利用TypeAt元函数找到对应的类型后再获取它的值。

另外标准库不使用这种形式最重要的原因就是如果你在tuple里存储了2个以上相同type的数据,会报错,很容易想到是为什么。

所以类似的技术更适合用于variant这样的对象,不过这里只是举例所以我们忽略了这些问题。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/c0c52608c23b37e352041892c54b0e9c.html