用模板类和模板函数是C++程序猿必须掌握的技能。然而要充分运用编译器的推导能力则不简单。
需求:建立一个表格类, 每一列的类型可以任意指定(编译期指定),每一行的元素可以任意指定(运行期确定)。列数可变(编译期指定),行数可变(运行期确定)。
这里特意强调了编译期和运行期,就是为了最大程度利用编译器的推导能力,进行编译期计算,以达到(接近)最高效率。
So, let‘s begin!!
将任务拆解,显然每列元素可以直接用stl的各种容器完成,这里暂时放下不表;行元素咋办呢?
Task1: 列数可变,但是在编译器能确定。
这里用到了C++11的Variadic Template特性。虽然之前也可以支持这种类似的写法,但是各种宏很容易把猿猿搞晕。还是新标准来得清爽。
// generic of TypeList
template <class... Type>
struct TypeList{};
// specific of TypeList
template <>
struct TypeList<>{};
// partial specific of TypeList
template <class HeadType, class... TailType>
struct TypeList<HeadType, TailType...> : public TypeList<TailType...>
{
typedef HeadType head_type;
typedef TypeList<TailType...> tail_typelist;
TypeList()
{
cout << "type: " << typeid(HeadType).name() << endl;
}
};
int main()
{
TypeList<int, char, double> typelist;
return 0;
}
// output:
// type: d
// type: c
// type: i
通过(编译期)递归形式达到类型和数量都可变的目的。
Task2: 如何获得第i列的类型, 好为get函数做准备?
这里有点小trick, 参考了stl的类型traits方法,也是通过递归形式获得类型:
// traits: typeOfIndex<index, typelist>
template <size_t _Index, class _TypeList>
struct typeOfIndex
{
typedef typename typeOfIndex<_Index - 1, typename _TypeList::tail_typelist>::value_type value_type;
};
template <class _TypeList>
struct typeOfIndex<0, _TypeList>
{
typedef typename _TypeList::head_type value_type;
};
非常优雅的完成了类型任务,以后只要写typeOfIndex<index, type_list>::value_type 就可以了。
Task3: 获取值的get函数如何定义,如何实现?
这个问题折腾了猿猿一整天。先看看我的容器定义吧:
// generic of ValueTable
template <class... ValueType>
class ValueTable/* : public ValueTable<TypeList<ValueType...> >*/{};
// specific of ValueTable
template <>
class ValueTable<>{}; // boundry
// specific of ValueTable
template <>
class ValueTable<TypeList<> >{};
// ValueTable offers real value (at runtime)
template <class... Types>
class ValueTable<TypeList<Types...> > :
public ValueTable<typename TypeList<Types...>::tail_typelist>
{
public:
typedef TypeList<Types...> type_list;
typedef typename type_list::tail_typelist tail_typelist;
typedef typename type_list::head_type value_type;
typedef ValueTable<type_list> __this_type;
typedef ValueTable<tail_typelist> __super;
private:
vector<value_type> m_val;
public:
ValueTable() = default;
ValueTable(const ValueTable&) = default;
ValueTable(ValueTable&&) = default;
};
第一个类是通式,没啥东西,就是告诉编译器:有这么个东西叫ValueTable,接收任何数量任何类型的参数的模板类。
第二和第三个是边界,类似递归出口,不解释。
第四个是真正的容器定义类,也是所有数据的存储地点。
那么,问题就来了:某个ValueTable怎么去获取指定索引的值呢?
看看我们手头有的东西:有个通过type_list和列索引转换到指定列类型的”函数“,列索引colIndex(编译期知道),行索引(运行期知道)。那么我们就通过这些来写出函数声明:
template<size_t colIndex>
typename typeOfIndex<colIndex, type_list>::value_type get(size_t keyIndex) const;
注意这里的输入参数只有两个: colIndex和keyIndex(可以看成rowIndex,这里命名成这个样子是有其它用途)。
有关实现这个函数。如果传入的colIndex为0,那么直接返回当前的m_val[keyIndex]就可以;否则,就在父类中递归找colIndex-1的值。
template<size_t colIndex>
typename typeOfIndex<colIndex, type_list>::value_type get(size_t keyIndex) const
{
return __super::get<colIndex-1>(keyIndex);
}
template<>
typename typeOfIndex<0, type_list>::value_type get(size_t keyIndex) const
{
return m_val[keyIndex];
}
看上去很美好,貌似任务都完成了。g++ -std=c++11 -c valuetable.cpp 试试?
不好,编译器报error!!难道是代码写错了?如果是RTTI代码,写错了在编译器也不知道,只有在运行时候才能看出些东西来。对于刚接触Generic Programming的猿猿来说还真不敢保证一定是对的。于是各种排查问题……(一下省略500字)
终于在stackoverflow中找到了答案:原来在模板类中是不允许进行成员函数的特化的,因为编译器并不知道到底要特化的是哪一个Model的成员函数。而且更加悲催的是VC程序猿编译这些代码居然可以通过!! 微软又大坑了一把各位猿猿们啊!!
OK,原因找到了,那么到底怎么实现呢?万能的stackoverflow告诉猿猿,可以采用函数重载的方法实现。写两个辅助函数,作为private函数就好了。当然,要重载就必须类型(签名)不同,那就在内部再加上一个identity类型标记好了。
// identity mark
template <size_t v>
struct identity{};
public:
template<size_t colIndex>
typename typeOfIndex<colIndex, type_list>::value_type get(size_t keyIndex) const { return get_aux(keyIndex, identity<colIndex>()); }
private:
// override function of get implement
template<size_t colIndex>
typename typeOfIndex<colIndex, type_list>::value_type get_aux(size_t keyIndex, identity<colIndex>) const
{ return __super::get<colIndex-1>(keyIndex); }
// specific of get<size_t, class>
typename typeOfIndex<0, type_list>::value_type get_aux(size_t keyIndex, identity<0>) const { return m_val[keyIndex];}
终于大功告成,编译通过。运行结果猿猿还没有测试。以上方法仅供参考。