模板类中定义模板函数

用模板类和模板函数是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];}


终于大功告成,编译通过。运行结果猿猿还没有测试。以上方法仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值