目录
- 13.1 非类型模板参数
- 13.2 函数模板的特化
- 13.3 类模板的特化
- 13.4 模板的分离编译
这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
个人主页:oldking呐呐
专栏主页:深入CPP语法口牙
13.1 非类型模板参数
- 顾名思义,非类型模板参数就是一个模板的参数,只不过不是类型,而是一个常量,这个常量的具体值会在被编译的时候确定下来,非类型模板参数也可以有缺省值,值得注意的是,在C++20之前,非类型模板参数的类型只能是整型(
int
,char
,bool
,short
,long
等)
template<int n = 10>
class AA
{
private:
int _aa[n];
};
//C++20支持了浮点数作为非类型模板参数
//template<double n = 10>
//class AA
//{
//private:
// double _aa = n;
//};
int main()
{
AA<10> a1;
AA<> a2;
//AA a3; (C++20)
return 0;
}
- 非类型模板参数比较常用的地方是一个叫做
array
的容器,其实array
和普通的数组几乎没有区别,只是说array
对于越界的检查会比普通的数组强得多
int main()
{
//非类型模板参数是数组的元素个数
array<int, 10> a1();
return 0;
}
13.2 函数模板的特化
-
模版的特化可以理解为模板的重载
-
比方说
template<class T>
void Less(T a, T b)
{
return a < b;
}
- 然后我想实现比较
int*
下的值,但原本的模板又没有解引用,所以就需要特化
template<>
bool Less<int*>(int* a, int* b)
{
return *a < *b;
}
-
这个特化中的类型,只能用已经有的类型,包括内置类型和自定义类型,用未知类型会直接报错
-
并且,特化的参数要和原模板几乎一样,意思是,特化要求特化能传的,原模版也要能传,只不过编译器会选择特化来调用罢了
-
不过从根本上说,函数特化其实并不好用,问题就出自上面这条"特化要求特化能传的,原模版也要能传",这就导致了一个重要的问题,假如说原模版使用了传引用传参而不是传值传参,就会出现很多问题
-
如果执意要用的话只能这么改
template<class T>
bool Less(const T& a, const T& b)
{
return a < b;
}
//因为const修饰的引用不能被修改,所以特化也要给被引用的值加const,这样看起来就会显得很奇怪
//所以还不如直接重载一个函数方便得多
template<>
bool Less<int*>(int* const& a, int* const& b)
{
return *a < *b;
}
- 反正都只能传
int*
,不如直接重载一个来得方便传值就传值,想传引用就传引用
bool Less(int* a, int* b)
{
return a < b;
}
13.3 类模板的特化
- 特化分为全特化和偏特化,有点像全缺省和半缺省
template<class T1, class T2>
class A
{
public:
A()
{
cout << "<class T1, class T2>" << endl;
}
};
//全特化
template<>
class A<int, char>
{
public:
A()
{
cout << "<int, char>" << endl;
}
};
//半特化
template<class T1>
class A<T1, char>
{
public:
A()
{
cout << "<T1, char>" << endl;
}
};
template<class T2>
class A<int ,T2>
{
public:
A()
{
cout << "<int ,T2>" << endl;
}
};
//特殊玩法
template<class T1, class T2>
class A<T1*, T2*>
{
public:
A()
{
cout << "<T1*, T2*>" << endl;
}
};
template<class T1, class T2>
class A<T1&, T2&>
{
public:
A()
{
cout << "<T1&, T2&>" << endl;
}
};
template<class T1, class T2>
class A<T1&, T2*>
{
public:
A()
{
cout << "<T1&, T2*>" << endl;
}
};
int main()
{
//正常调用
A<float, double> a1;
//特化会优先调用最匹配的,也就是全特化,然后才是偏特化
A<int, char> a2;
A<float, char> a3;
A<int, double> a4;
A<float, double> a5;
//特殊玩法:你甚至可以指定指针和引用(也是偏特化)
A<float*, double*> a6;
A<float&, double&> a7;
A<float*, double&> a8;
return 0;
}
-
偏特化其实就是在原模版的基础上做一些限制,而全特化则是在偏特化的基础上做更更多的限制,限制到只能使用具体的类型
-
特化一般用在需要多场景使用的函数/类/仿函数上,比方说既想实现对值的比较,又想实现对指针下值的比较,就可以再特化一个模板出来
-
模板特化需要注意,例如以下这种情况
template<class T1, class T2>
class A<T1*, T2*>
{
public:
A()
{
cout << "<T1*, T2*>" << endl;
T1 a;
}
};
- 此时这个
a
是原类型T1
而不是T1*
,这样设计可以同时在模板里面使用原类型和引用/指针
13.4 模板的分离编译
-
简单来说
-
正常函数的声明和定义分离可以正常被调用,因为在链接的过程中,函数是被确定的,是可以被找到的,不像是函数模板一样不被确定,需要临时生成
-
模板因为含有未知类型,调用的源头会在编译的时候找到函数声明,然后根据传入的参数类型对模板进行实例化,实例化之后接可以直接用
-
但如果声明和定义分离了,调用的源头会在编译的时候找到函数声明但却没有找到含函数定义,这时候会假设存在这样的函数,但此时定义还是个模板,并没有被实例化,所以在找函数的时候,就会找不到,所以如果要进行声明和定义分离的话,就一定要函数定义实例化,这样才能在链接的时候有函数可找
-
一般显式实例化只需要在定义的下面这么写就行
//func.cpp
#include"func.h"
#include<iostream>
using namespace std;
//模板的定义
template<class T>
void func(const T&, const T&)
{
cout << "void func(const T&, const T&)" << endl;
}
//显式实例化
template //这个是作为函数模板1显式实例化的标志
void func(const int&, const int&); //这里要填入具体参数
- 所以事实上我们还是建议在
.h
直接定义函数模板,最好还是不要在.cpp
里显式实例化