【模板进阶】综合范例

综合范例

本篇博客主要是在之前学习的基础上,综合应用一下内容,加深理解。


1. 简单类型的实现

首先我们有两个大小不同的 v e c t o r vector vector容器,这两个容器的元素类型不同,比如一个是 i n t int int,一个是 d o u b l e double double,我们希望设计一个算法,让两个 v e c t o r vector vector容器里的内容相加,返回一个类型和大小合适的 v e c t o r vector vector


显然,我们需要重载加法运算符,保证两数相加返回正确的类型。
因此,我们引入一个新的类模板:

//推断返回值
template<typename T, typename U>
struct VecAddResult {
	using type = decltype(T() + U()); //创建临时对象
};

这里利用到了 d e c l t y p e decltype decltype来推断 T ( ) T() T() U ( ) U() U()这两个临时变量相加的类型。


而对于大小,我们返回的容器一定是较大的那个容器的大小,因此,我们可以这样重载加法运算符:

template<typename T,typename U>
std::vector<typename VecAddResult<T, U>::type>operator+(std::vector<T>const&vec1,std::vector<U>&vec2) {
	std::vector<typename VecAddResult<T, U>::type>res(std::max(vec1.size(),vec2.size()));
	for (int i = 0, j = 0; i < vec1.size() || j < vec2.size(); ++i, ++j) {
		if (i < vec1.size() && j < vec2.size()) res[i] = vec1[i] + vec2[j];
		else {
			res[i] = (i < vec1.size()) ? vec1[i] : vec2[j];
		}
	}

	return res;
}

看起来很简单,注意 V e c A d d R e s u l t VecAddResult VecAddResult前面的 t y p e n a m e typename typename。 注意的是,这样的重载只适用于能够通过 d e c l t y p e decltype decltype推断出来的类型,对于自定义类型,我们不能这样写。


我们运行一下,看看结果:

void Test1() {
	std::vector<int>vec1 = { 1,2,3,4,5 };
	std::vector<double>vec2 = { 1.1,2.1,3.1,4.1,5.1,6.1 };

	auto res = vec1 + vec2;

	for (auto& x : res) {
		std::cout << x << " ";
	}
	std::cout << "\n";

}

结果与预期一样:

在这里插入图片描述

2.自定义类型的实现

2.1默认构造

如果是自定义类型,并且具有默认构造,然后我们也能够通过 d e c l t y p e decltype decltype来推导

例如下面的例子,我们重载了减法操作符:

struct elem {
	//重载一下+运算符
	elem operator-(const elem);
	elem() {}
};

template<typename T, typename U>
struct VecSubResult {
	using type = decltype(T() - U()); //创建临时对象
};

//重载运算符-
template<typename T, typename U>
std::vector<typename VecSubResult<T, U>::type>operator-(std::vector<T>const& vec1, std::vector<U>const& vec2) {
	std::vector<typename VecSubResult<T, U>::type>res;

	//...中间省略具体实现

	return res;
}

void Test2() {
	std::vector<elem>vec1;
	std::vector<elem>vec2;

	 auto res = vec1 - vec2;
}

其中具体的实现内容与类的具体使用有关,这里只做最基本的演示。

3.1 有参构造

如果一个自定义类只存在有参构造呢,如何使用 d e c l t y p e decltype decltype呢? 实际上,我们可以使用 d e c l v a l declval declval来避免直接构造类型,如下:

//如果是带有参数的构造函数

struct elemC{
	int val;
	elemC(int _val) :val(_val) {};

	elemC operator*(const elemC&);
};


template<typename T, typename U>
struct VecMulResult {
	using type = decltype(std::declval<T>()* std::declval<U>()); //使用std::declval不需要创建临时变量
};

//重载*运算符
template<typename T, typename U>
std::vector<typename VecMulResult<T, U>::type>operator*(std::vector<T>const& vec1, std::vector<U>const& vec2) {
	std::vector<typename VecMulResult<T, U>::type>res;

	//...中间省略具体实现

	return res;
}

void Test3() {
	std::vector<elemC>vec1,vec2;

	auto res = vec1 * vec2;
}

使用 d e c l v a l declval declval不会调用构造函数,因此也就不会创建临时对象了,这样就与构造函数无关了。


进一步的,我们可以使用别名模板来简化这个冗长的返回值:

template<typename T,typename U>
using VecMulResult_t = typename VecMulResult<T, U>::type;

//此时简化为
template<typename T,typename U>
std::vector<VecMulResult_t<T,U>>operator*(std::vector<T>const& vec1, std::vector<U>const& vec2) {
	std::vector<VecMulResult_t<T,U>>res;

	//...中间省略具体实现

	return res;
}

这样的简化与绝大多数标准库的别名模板类似,比如使用 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t来简化 s t d : : e n a b l e _ i f < > : : v a l u e std::enable\_if<>::value std::enable_if<>::value等等。

2.3 两个类型无法运算

理想情况下,我们调用的类型都是可以运算的,但是我们不能保证一些类型是无法进行运算的。因此我们需要在调用无法运算的两个类型的时候及时报错。


例如,我们使用浮点数 d o u b l e double double取模 ( % ) (\%) (%)整型 i n t int int,显然这是错误的,要让书写的时候就报错。
因此我需要使用到 s t d : : v o i d _ t std::void\_t std::void_t d e c l v a l declval declval,借助 S F I N A E SFINAE SFINAE的特性,来判断是否可以推导出类型,类似一种编译期间的分支的操作,具体如下:

//泛化版本
template<typename T,typename U,typename V = std::void_t<>>
struct IfCanMod :std::false_type {
};

//特化版本
template<typename T,typename U>
struct IfCanMod<T,U,std::void_t<decltype(std::declval<T>()%std::declval<U>())>>
	:std::true_type {
};

我们分别让泛化版本和特化版本继承了 f a l s e _ t y p e false\_type false_type t r u e _ t y p e true\_type true_type,这样我们就能知道如果能正确取模,那么一定有一个值 v a l u e = t r u e value = true value=true


接着,让我们实现一下返回值的推导:

//泛化版本
template<typename T, typename U, bool ifcando = IfCanMod<T, U>::value>
struct VecModResult {
	using type = decltype(std::declval<T>() % std::declval<U>());
};

//特化版本
template<typename T, typename U>
struct VecModResult<T,U,false> {
};

//简化模板名称
template<typename T, typename U>
using VecModResult_t = typename VecModResult<T, U>::type;

可以发现,如果存在 v a l u e = t r u e value = true value=true,会调用泛化版本,那么就存在返回值 t y p e type type。反之,就会调用特化版本,不存在返回值 t y p e type type


因此最后我们实现一下重载取模操作符:

//重载operator%
template<typename T, typename U>
std::vector<VecModResult_t<T, U>>operator%(std::vector<T>const& vec1, std::vector<U>const& vec2) {
	std::vector<VecModResult_t<T, U>>res;

	//...中间省略具体实现

	return res;
}

让我们测试一下:

void Test4() {
	std::vector<long long>vec1;
	std::vector<int>vec2;
	std::vector<double>vec3;

	auto res = vec1 % vec2; //编译成功

	auto res2 = vec2 % vec3; //编译失败,无法操作
}

可以发现,当浮点型和整型取模的时候,就自动报错了:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值