c++ 模版 萃取

1,为什么要有萃取

考虑以下代码
示例:

template<typename T>
T add(T a, T b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = add(a, b);
	cout << c << endl;
}

返回值可以由模版参数推导出来,如果是如下情况:

template<typename U , typename T>
U add(T a, T b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = add<int>(a, b);
	cout << c << endl;
}

因为模版参数U无法由add模版函数的参数列表推导,所以调用时需要指出

似乎模版函数还能应对,但是考虑以下代码:

template<typename U , typename T>
U add(T a, T b)
{
	return a + b;
}
int main()
{
	int a = 2000000000;
	int b = 2000000000;
	int c = add<int>(a, b);
	cout << c << endl;
}

返回值会发生溢出的情况,如果我们要防止溢出就需要返回 long long 类型,而不是 int 类型,也就是返回值类型不能由单纯的模版参数 TU 推出,这时模版参数就无能为力了

当然你可以使用auto来完成任务,这里只是应用的举例,如果感到不够深刻,来看下面一个例子:
count 方法是STL的一个通用方法,它的作用是寻找相同的元素并返回相同元素的个数。现在我们来实现它:

template<typename T>
int My_Count(T begin, T end, <--!类型是什么 --> data)
{
	int count = 0;
	T ptr = begin;
	while (ptr != end)
	{
		if (*(ptr) == data)
		{
			count++;
		}
		ptr++;
	}
	return count;
}

实现细节无关紧要,真正的问题在于data也就是我们要比较的元素到底是什么类型。因为它是STL标准库的算法,默认接受的也一定是迭代器,我们该怎么获取迭代器所指向的元素类型。
下面来解决:

template<typename T>
struct My_type_traits{
	using value_type = typename T::value_type;
};

看不懂没关系,我来解释一下。这是一个内嵌型别声明,我更喜欢叫它内嵌类型声明,语法为: using 类型别名 = typename 类型原名 ,typename加上与否取决于类型原名是否为内置类型,建议统一加上。还有一点需要注意,内嵌类型只作用于声明它的类或结构体中,相当于类的成员,又因为它独一份,所以类似于静态成员。这个的意义在于我们可以像访问静态成员一样访问内嵌类型,像这里的 T::value_type 就是访问的 T 之中的 value_type 类型。T 的目标类型是迭代器,也就是说这个结构体假设了传入的迭代器都声明了内嵌型别 vaule_type ,事实上所有的迭代器也是这么做的。value_type 保存了迭代器指向数据的数据类型,这里又将其重定义为 My_type_traits 结构体下的value_type ,所以我们可以改写以上 count 函数:

template<typename T>
int My_Count(T begin, T end, typename My_type_traits<T>::value_type data)
{
	int count = 0;
	T ptr = begin;
	while (ptr != end)
	{
		if (*(ptr) == data)
		{
			count++;
		}
		ptr++;
	}
	return count;
}

这里的代码并不严谨,因为原生指针不可能有内嵌型别,下面的类型萃取的示例代码可以帮我们完成这一任务
这里的 typename 的意思是后面跟的是一个类型名

总结: 萃取应用于变量类型的智能推导,比如函数的返回值类型
2,萃取的应用

对于上面模版参数为int却要返回long long类型,可以采取以下方法:

template<typename T>
struct type_traits;

template<>
struct type_traits<int>
{
	using end_type = int64_t;
};


template<typename T>
typename type_traits<T>::end_type add(T a, T b)
{
	using type = typename type_traits<T>::end_type;
	type sum{};
	sum = a;
	sum += b;
	return sum;
}
int main()
{
	int a = 2000000000;
	int b = 2000000000;
	auto c = add(a, b);
	cout << c << endl;
}

其中 type_traits 的一个特化实例接受 int 型,并将 end_typeint64_t 绑定,实现了 intint64_t 出的效果,实现了返回值类型的推导,这就是 固定萃取

必须指出的是不能直接返回 a+b ,因为 int64_t 类型的转换发生在 a+b 之后,即已经溢出的情况下

接下来我们引入另一个案例:

template<typename T>
void My_Copy(T* old, T* new_data, size_t size)
{
	memcpy(new_data, old, size * sizeof(T));
}

int main()
{
	int array[5] = { 1,2,3,5,6 };
	int buff[5]{};
	My_Copy(array, buff, 5);
	for (int i = 0; i < 5; i++)
	{
		cout << buff[i] << endl;
	}
}

这里我们定义了一个 copy 函数用来复制,当T内置变量类型时,它可以很好的胜任,但是如果T为带指针的自定义类型,因为 memcpy 本身是浅复制,无法很好的复制带指针的自定义类型,就必须采取下面的函数版本

template<typename T>
void My_Copy(T* old, T* new_data, size_t size)
{
	for (size_t i = 0; i < size; i++)
    {
        *(new_data + i) = *(old + i);
    }
}

int main()
{
	int array[5] = { 1,2,3,5,6 };
	int buff[5]{};
	My_Copy(array, buff, 5);
	for (int i = 0; i < 5; i++)
	{
		cout << buff[i] << endl;
	}
}

这个版本的函数也可以很好的代替上一个版本的函数并完成上一个版本的函数的工作。但是相比较与 for 循环而言, memcpy 的效率具有压倒性的优势, c++ 是一门追求效率的语言,怎么样在尽可能多的情况下使用 memcpy 而不是 for 循环就成了问题

值萃取 应运而生

template<typename T>
struct value_traits
{
	static const bool value = false;
};


template<>
struct value_traits<int>
{
	static const bool value = true;
};

template<typename T>
void My_Copy(T* old, T* new_data, size_t size)
{
	if (value_traits<T>::value)
	{
		memcpy(new_data, old, size * sizeof(T));
	}
	else
	{
		for (size_t i = 0; i < size; i++)
        {
            *(new_data + i) = *(old + i);
        }
	}
}

int main()
{
	int array[5] = { 1,2,3,5,6 };
	int buff[5]{};
	My_Copy(array, buff, 5);
	for (int i = 0; i < 5; i++)
	{
		cout << buff[i] << endl;
	}
}

值萃取是给定一个类型返回一个值。当类型为 intvaluetrue ,不为 intvaluefalse 。这里只罗列了传入 int 时的模版特化,事实上需要特化所有内置类型。

如果我要传入指针类型返回一个指针指向的对象的引用就会用到 类型萃取

template<typename T>
struct My_remove_pointer;

template<typename T>
struct My_remove_pointer<T*>
{
	using value_type = typename T;
};

template<typename T>
struct My_add_value_ref
{
	using value_type = typename T&;
};

这里用到了模版的偏特化。这种类型转换不同于固定萃取的是T可以为任意值。类型萃取和值萃取并无严格区分,不同的叫法只是习惯,事实上类型萃取也可以用于类型判断和类型关系判断,返回 true or false

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值