Fake Variant
很久之前就想过做这样一个结构,这个结构可以存放各种各样的类型,并且可以根据需要从这个结构里取出这些类型的值。 当时用void
指针做过,但是这不是类型安全的,也尝试过为所有基础类型做一个wrapper,使它们都派生自一个共同的基类(类似Java这样),然后用户自定义类型只需要继承这个基类,这样就可以利用多态实现动态分发,实际做了个heterogenous container:
不幸的是,这个容器存在内存泄漏(std::vector的实现里面)。
近来,又拾起尚未读完的C++ Modern Design从头开始阅读,直到昨天读到了利用TypeList
生成类
template<typename TL, template<typename> class Class> class GenClasses;
template<typename T1, typename T2, template<typename> class Class>
class GenClasses<TypeList<T1, T2>, Class> : public GenClasses<T1, Class>,
public GenClasses<T2, Class>
{};
template<typename T, template<typename> class Class>
class GenClasses : public Class<T>
{};
template<template<typename> class Class>
class GenClasses<Nil, Class>
{};
利用C++独有的特性(template template parameters)在编译期自动为指定类型生成继承关系,按照书上的例子
template<typename T> struct Holder { T value; };
using WidgetInfo GenScatterHierarchy<TYPELIST_3(int, string, Widget), Holder>;
以上代码将会生产下图所示的继承关系:
看到这里,顿时感到醍醐灌顶!于是有了利用这个事实写一个variant的想法。利用C++11起提供的variadic template写下了如下代码
template<typename... Args> struct GenList;
template<> struct GenList<Nil>
{
using type = Nil;
};
template<typename T>
struct GenList<T>
{
using type = TypeList<T, Nil>;
};
template<typename T, typename... Args>
struct GenList<T, Args...>
{
using type = TypeList<T, typename GenList<Args...>::type>;
};
今天晚上有发现上面的代码并不完美,因为它会把Nil
也给计算进去,比如Len<GenList<int, Nil, Nil>::type>::len
会是3,而期望值是1,所以又加上的下面的代码
template<typename... Args>
struct GenList<Nil, Args...> // ignore `Nil` in Args
{
using type = typename GenList<Args...>::type;
};
针对模板参数中的Nil
进行偏特化。
再为FakeVariant
类添加了emplace
方法,但是原有的set
方法同名,作重载用,用在Non-Copyable结构上。
至此,一个简单的variant就可以用了:
源码请点这里
最后,感谢 Andrei Alexandrescu 为我们带来这样一本传世之作!
PS: C++ 17已提供variant支持,以及any、optional,但是不幸的是目前GCC 6.2.1中并没有发现variant支持,预计在GCC7实验性支持以上3个特性,详情点击这里。