初尝类型萃取--typename、模板偏特化、和traits之(三)类型萃取
目录
当模板遇到迭代器和指针
类型萃取是如何解决上述问题的
本文作者在看过公众号《C++学习与探索》的文章《【一分钟学习C++】萃取机制》后,对类型萃取(type_traits)有了初步的认识,所以写下这篇博客。《【一分钟学习C++】萃取机制》门槛较高,如果没有理解typename和模板偏特化,是难以理解该文意思的。本文以及前面的两篇文章(初尝类型萃取--typename、模板偏特化、和traits之一、二)目的是将萃取涉及的基本知识点,如typename、模板偏特化先讲清楚,然后通过讲解例子的方式,使得类型萃取能被更多读者理解。
当模板遇到迭代器和指针
看下面例子:
#include <iostream>template <typename T>
class tmp
{
public:*T ret(T s) {//编译失败,用auto代替*T是可以的return *s;}
};
int main()
{int p = 1;tmp<int*> t;int i = t.ret(&p);std::cout << "i = "<<i<<"\n";return 0;
}
在C++标准里,你可以用*指针 的方式获取一个变量,但是不能用*类型。上面的例子使用*T是失败的。
同样,使用迭代器也有类似的问题:
#include <vector>
#include <iostream>template <typename T>
class tmp
{
public:*T ret(T s) {//编译同样失败return *s;}
};
int main()
{std::vector<int> vec{ 0,1,2 };std::vector<int>::iterator itr = vec.begin();tmp< std::vector<int>::iterator> t;int i = t.ret(itr);std::cout << "i = "<<i<<"\n";return 0;
}
正如前面所说,在C++标准里,你可以用*指针 的方式获取一个变量,但是不能用*类型。我们可以用auto替代*T的写法。另外,我们也可以采用萃取技术。
类型萃取是如何解决上述问题的
类型萃取主要依赖两个技巧:
1 使用typename
2 模板偏特化
看下面的代码:
#include <type_traits>
#include <iostream>//先准备非特化的模板
template <typename T>
struct itr_trait;
//特化模板
template <typename T>
struct itr_trait<T*>
{//using valuetype = T;//等效于下面的typedef typename T valuetype;itr_trait(T * t) {i = *t;s = t;}valuetype i;valuetype* s;valuetype getS() { return *s; }
};int main()
{int a = 2;itr_trait<int *> t(&a);std::cout <<"i = "<<t.i << ",*s = "<<*t.s<<",t.s is int * "<<(std::is_same<int*, decltype(t.s)>::value ? std::string("true") : std::string("false")) << std::endl;std::cout << "getS() = " << t.getS() << std::endl;std::cin.get();return 0;
}
main函数中的itr_trait<int *>匹配了特化模板itr_trait<T *>。所以T = int。
由于typedef typename T valuetype这句,valuetype = T = int
由于valuetype * s这句,s的类型就是int *
getS()的返回值就是int。这样也就解决了文章开头* T编译失败的问题。
再来看看《【一分钟学习C++】萃取机制》里面更加复杂的示例:
// class type
template <class T>
struct iterator_traits {//用来处理迭代器的情况typedef typename T::value_type value_type;//迭代器T一定含有T::value_type这个类型
};
// 指针类型偏特化,用来处理指针的情况
template <class T>
struct iterator_traits<T*> {typedef T value_type;
};
// const指针类型偏特化,用来处理指针的情况
template <class T>
struct iterator_traits<const T*> {typedef T value_type;
};template <class T>
typename iterator_traits<T>::value_type//这定义func函数的返回值类型
func(T iter) {std::cout << "normal version" << std::endl;return *iter;
}
根据cppreference的说法std::iterator一定含有value_type这个成员。
在下面的例子里,T = vector<int>::iterator。func(T iter)里面的T是迭代器(而不是指针) ,则模板匹配非特化的情况。此时,由于
typedef typename T::value_type value_type;
iterator_traits<T>::value_type就等于vector<int>::iterator的value_type(value_type一定是iterator的成员),也就是int
// class type
template <class T>
struct iterator_traits {//用来处理迭代器的情况typedef typename T::value_type value_type;//迭代器T一定含有T::value_type这个类型
};template <class T>
typename iterator_traits<T>::value_type//这定义func函数的返回值类型
func(T iter) {std::cout << "normal version" << std::endl;return *iter;
}vector<int> vec;
vector<int>::iterator itr = vec.begin();
所以func函数的返回值类型iterator_traits<T>::value_type一定是vector<int>::iterator的value_type,也就是int。
假如func(T iter)的T类型是指针int *,则非特化模板不能与之匹配,因为int *没有value_type成员,使得
typedef typename T::value_type value_type;
不成立。
这时,特化模板就起作用了。
// 指针类型偏特化,用来处理指针的情况
template <class T>
struct iterator_traits<T*> {typedef T value_type;
};
// const指针类型偏特化,用来处理指针的情况
template <class T>
struct iterator_traits<const T*> {typedef T value_type;
};template <class T>
typename iterator_traits<T>::value_type//这定义func函数的返回值类型
func(T iter) {std::cout << "normal version" << std::endl;return *iter;
}int w = 2;
func<int *>(&w);
下面这句将强制给iterator_traits加上一个成员类型value_type,且value_type = T。
typedef T value_type;
于是func模板展开成了下述形式:
iterator_traits<int*>::value_type//这定义func函数的返回值类型
func(int * iter) {std::cout << "normal version" << std::endl;return *iter;
}
匹配了下面的模板:
template <class T>
struct iterator_traits<T*> {typedef T value_type;
};
//匹配为:
struct iterator_traits<int *>{typedef int value_type;
};
故func函数返回值仍然是int。