C++Primer学习笔记-模板特例化
⽬录
模板特例化由来
单⼀模板对任何可能的模板实参并总是合适,并且能实例化的。⽐如,compare的例⼦,⽤于⽐较2个实参:
// 第⼀个版本:⽐较任意两个类型
template <typename T>
int compare(const T&, const T&);
// 第⼆个版本:可以⽐较字符串字⾯量
template <size_t N, size_t M>
int compare(const char (&)[N], const char (&)[N]);
第⼀个版本compare可⽤于⽐较任意两个类型,第⼆个版本可以⽤于⽐较字符串字⾯量。然⽽,当实参是2个字符串对应指针时,我们希望的是⽐较2个字符串⼤⼩,⽽第⼀个版本会直接⽐较2个指针⼤⼩,第⼆个版本⽆法将指针转化为⼀个数组的引⽤(因为编译器⽆法直接通过实参指针判断这是个什么类型)。因此,这2个版本compare都是不⾏的。
为了解决这个字符指针的问题,我们引⼊模板特例化(template specialization)。⼀个特例化版本就是模板的⼀个独⽴定义,其中⼀个或多个⽬标参数被指定为特定的类型。另外,模板特例化在有的书籍简称“模板特化”。
定义函数模板特例化
可以为第⼀个版本的compare定义⼀个模板特例化版本。以template<>开头,“<>” 表⽰我们将为原模板的所有模板参数提供实参:
// compare的特例化版本,⽤于处理字符串数组的指针的⽐较
template<>
int compare(const char* const &p1, const char* const &p2)
{
return strcmp(p1, p2);
}
当我们定义⼀个特例化版本时,函数参数类型必须与先前声明的模板中对于的类型匹配。我们特例化的原先模板:
template <typename T>
int compare(const T&, const T&);
定义的特例化中,T为const char *(底层const),第⼀个版本中const是⽤来表⽰形参a为const &(顶层const),如果直接写成const (const char*) &a,则等价于const char * &a,并不能表⽰引⽤本⾝⽆法修改,因此将const移动到形参a处(顶层const),最终结果就是const char* const& a。
函数重载与模板特例化
定义⼀个函数模板的特例化版本本质上是为原模板的⼀个特殊实例提供定义,⽽⾮函数名的⼀个重载版本。
当对字符串字⾯常量调⽤compare时,
compare("hi", "mom");
compare的版本⼆和版本三特例化版本都可⾏,提供同样精确的匹配,但是接受字符数组参数的版本更特例化,因此编译器或选择版本⼆。如果将接受字符指针的compare版本修改定义为⼀个普通的⾮模板函数,在通⽤精确的匹配时,编译器会优先选择⾮模板函数。
Tips:模板及其特例化版本应该声明在同⼀个头⽂件中,位于同⼀个作⽤域。所有同名模板的声明应该放在前⾯,然后是这些模板的特例化版本。避免位于不同⽂件,特例化版本丢失时(或者不在同⼀个作⽤域),编译器依然可以⽤原有的模板进⾏匹配,不过可能并不是我们想要的结果。这种问题通常难以查。
类模板特例化
前⾯讲了函数模板可以特例化,类模板同样也可以特例化。STL hash模板可以⽤于计算对象的hash值,我们以hash模板定义⼀个特例化版本为例,⽤它来保存Sales_data对象。
⼀个特例化hash类必须定义:
⼀个重载的调⽤运算符operator(),它接受⼀个容器关键字类型的对象,为给定类型计算hash值,返回⼀个size_t;
两个类型成员:result_type和argument_type,分别调⽤运算符的返回类型和参数类型;
默认构造函数和拷贝赋值运算符;
参考
template <class T> class std::hash; // 声明友元函数所需
class Sales_data {
friend class std::hash<Sales_data>;
private:
string bookNo;
unsigned units_sold;
double revenue;
};
/
/ 打开std命令空间,特例化std::hash
namespace std {
template<>
struct hash<Sales_data>
{
typedef size_t result_type;
typedef Sales_data argument_type;
size_t operator()(const Sales_data& s) const;
};
size_t hash<Sales_data>::operator()(const Sales_data& s) const
{
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
} // 关闭std命名空间
// 客户端,利⽤特化版本hash<Sales_data>求Sales_data对象hash值template<>
struct my_is_void<void> {
static const bool value = true;
};
int main()
strcmp比较数组{
auto p = new hash<Sales_data>();
Sales_data s;
size_t d = p->operator()(s);
cout << d << endl;
d = hash<Sales_data>()(s);
cout << d << endl;
return 0;
}
类模板部分特例化
与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定⼀部分⽽⾮所有模板参数,或者参数的⼀部分⽽⾮全部特性。⼀个类模板的部分特例化(partial specialization,也称偏特化)本⾝是⼀个模板,使⽤它时⽤户必须为那些在特例化版本中未指定的⽬标参数提供实参。
// 以下内容来⾃STL库
// 原始的、最通⽤的版本
// STRUCT TEMPLATE remove_reference
template<class _Ty>
struct remove_reference
{ // remove reference
using type = _Ty;
};
// 部分特例化版本,⽤于左值引⽤和右值引⽤
template<class _Ty>
struct remove_reference<_Ty&> // 只适⽤于左值引⽤的特例化版本
{ // remove reference
using type = _Ty;
};
template<class _Ty>
struct remove_reference<_Ty&&> // 只适⽤于右值引⽤的特例化版本
{ // remove rvalue reference
using type = _Ty;
};
部分特例化的“部分”在于模板参数列表是原始模板的参数列表的⼀个⼦集,或者⼀个特例化版本。
所谓⼦集,包含2个⽅⾯:模板参数的数⽬,模板参数的类型。本例中,特例化版本的模板参数数⽬与原始版本相同,但类型不同,属于原始版本的⼦集。
两个特例化版本分别只能⽤于左值引⽤和右值引⽤:
int i;
// decltype(42)为int,使⽤原始模板
remove_reference<decltype(42)>::type a;
// decltype(i)为int&,使⽤第⼀个(T&)部分特例化版本
remove_reference<decltype(i)>::type b;
// decltype(std::move(i))为int&&,使⽤第⼆个(T&&)部分特例化版本
remove_reference<decltype(std::move(i))>::type c;
a,b,c均为int类型。
特例化成员⽽不是类
可以只特例化成员函数⽽不特例化整个模板。
例如,只特例化类模板Foo的成员函数Bar:
template <typename T>
struct Foo {
Foo(const T& t = T ()) : mem(t) { }
void Bar() { /* ... */ } // 通⽤Bar函数版本
T mem;
// ...
};
// 类模板Foo的Bar成员特例化版本, 只有Foo<int>对象,才会调⽤该特例化函数版本. 其他的调⽤通⽤Bar函数
template<>
void Foo<int>::Bar()
{
// 进⾏T为int的特例化处理
}
// 客户端
Foo<string> fs; // 实例化Foo<string>::Foo()
fs.Bar(); // 实例化Foo<string>::Bar()
Foo<int> fi; // 实例化Foo<int>::Foo()
fi.Bar(); // 使⽤特例化函数版本Foo<int>::Bar()
模板特例化与模板实例化
注意区分模板特例化与模板实例化。
模板实例化
是指编译器根据根据调⽤函数给定的函数实参,或者定义类时给定的显⽰模板参数列表,推断出对应模板参数类型,并⽤实参类型替换模板参数,创建出⼀个新的“实例”,这个过程叫模板实例化。
特征:任何模板(函数模板或类模板),都必须实例化后才能被⽤户使⽤。
模板特例化
是指为解决通⽤模板不能很好解决特定类型匹配问题,⽽专门设置的独⽴模板。
即使是模板特化,也许需要实例化,才能为⽤户所使⽤;但即使没有特化版本,也可以匹配通⽤版本的模板,然后实例化为⽤户所⽤。
特征:
1)必须有通⽤的函数模板或类模板,特例化只针对少数特定类型额外定义的模板;
2)形式上以template<>开头;
模板特例化与模板偏特化
模板特化是为通⽤模板定义⼀个特殊情况,模板参数是某个固定的具体类型,⽐如void,char,string等(每个特化模板只能固定⼀个)。函数模板,类模板都可以特例化。
偏特化的模板参数是通⽤模板参数的⼦集(个数和类型),并不是固定的具体类型,⽽是⼀类类型,⽐
如T可以匹配char, int,string等(每个偏特化模板可以匹配⼀类)。只能偏特化类模板,不能偏特化函数模板。为什么?因为针对参数类型不同、个数不同的函数,完全可以⽤函数重载来实现,也就没有必要对函数模板偏特化。
// 特化例⼦
template<typename T>
struct my_is_void {
static const bool value = false;
};
template<>
struct my_is_void<void> { //特化版本,只有my_is_void<void>(模板参数是void)的对象,才能匹配到该特化版本
static const bool value = true;
};
// 偏特化例⼦
template<typename T>
struct my_is_pointer {
static const bool value = false;
};
template<typename T>
struct my_is_pointer<T*> { // 偏特化版本,任意my_is_pointer<T*>(模板参数是指针类型)的对象,才能匹配偏特化版本
static const bool value = true;
};
int main()
{
my_is_void<bool> t1; // 使⽤通⽤版本my_is_void实例化,T为bool
cout << t1.value << endl; // 打印0
my_is_void<void> t2; // 使⽤特例化版本my_is_void实例化,T为void
cout << t2.value << endl; // 打印1
my_is_pointer<char> p1; // 使⽤通⽤版本my_is_pointer实例化,T为char
cout << p1.value << endl; // 打印0
my_is_pointer<char *> p2; // 使⽤偏特化版本my_is_pointer实例化,T为char* cout << p2.value << endl; // 打印1
return 0;
}Processing math: 100%
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论