函数模板
定义
cpp
template <typename T>
T sub(T a,T b)
{
return a-b;
}
实例化
cpp
int main() {
int x=sub<int> (1,2);
double y=sub<double> (2.1,1.2);
// std::string s=sub<std::string>("212","2131"); // 编译错误,std::string不支持'-'操作。
return 0;
}
编译阶段,在对模板实例化前,编译器会判断函数体对于该类型是否可以实例化,若不行则编译错误。
模板参数推断
在推断模板类型时,不能进行自动类型转换,要求类型精确匹配。
cpp
template <typename T>
T sub(T a,T b)
{
return a-b;
}
sub(3,4.0); // 不被允许
其中T的推导规则,请见:
函数模板重载
函数模板重载:函数名称相同,参数数量或参数类型不同。
函数和函数模板可以同时存在,当函数模板和函数都合适时,优先调用函数。可使用<>指定调用函数模板。
cpp
template<typename T>
T sub(T a,T b)
{
std::cout<<"call sub(T,T)\n";
return a-b;
}
template<typename T>
T sub(T a)
{
std::cout<<"call sub(T)\n";
return a--;
}
int sub(int a,int b)
{
std::cout<<"call sub(int,int)\n";
return a-b;
}
int main() {
sub(1,2);
sub<>(1,2);
sub(1);
return 0;
}
/*
输出:
call sub(int,int)
call sub(T,T)
call sub(T)
*/
特化
特化模板可用于针对特别的模板参数,即某种特别类型做单独的特殊处理。
特化的模板版本相当于泛化模板的一个子集,特化的模板通过泛化的模板生成。故,先有泛化模板,才有特化模板。
全特化
全特化等价于实例化一个函数模板,并不等价于函数重载,即全特化的函数模板和相同参数的函数可以同时存在。
cpp
// 泛化版本
template<typename T>
T sub(T a,T b)
{
std::cout<<"call sub(T,T)\n";
return a-b;
}
// 针对std::string的全特化版本
template<>
std::string sub<std::string>(std::string a,std::string b)
{
std::cout<<"call sub<std::string>(T,T)\n";
int x=a.size()-b.size();
return std::to_string(x);
}
// 函数
std::string sub(std::string a,std::string b)
{
std::cout<<"call sub(std::string,std::string)\n";
int x=a.size()-b.size();
return std::to_string(x);
}
当自动推导调用时,如sub("123","345");,其调用优先级为:普通函数>特化版本>泛化版本。
偏特化
并不存在函数模板的偏特化,都通过函数模版重载实现。
类模板
定义
cpp
template <typename T>
class A
{
public:
A(T t):_t(t){}
void show();
private:
T _t;
};
template<typename T>
void A<T>::show(){ std::cout<<_t<<std::endl;}
类模板特化
特化版本的类模板可认为是和泛化模板完全不同的类,在其中可以重新定义不同的成员或函数。
全特化
- 全特化类模板
全特化的类模板可以当作一个明确的普通类使用。
cpp
template <typename T,typename C>
class TC {};
// 全特化版本
template<>
class TC<int,int>
{
void show();
};
// 不需要
// template<>
void TC<int,int>::show()
{
std::cout<<"TC<int,int>.show\n";
}
- 全特化普通成员函数,全特化静态变量
若是全特化了普通成员函数,或者全特化了静态变量,则无法使用该全特化的特定类型再进行类模板的全特化。因为全特化普通成员函数或者静态变量,本质上会实例化对应版本的类,若是再对该版本类进行全特化会造成重复实例化。
cpp
template <typename T,typename C>
class TC
{
void show()
{
std::cout<<"TC<T,C>::show\n";
}
static std::string str;
};
// 全特化普通成员函数
template<>
void TC<double,int>::show()
{
std::cout<<"TC<double,int>::show\n";
}
// 全特化静态变量
template<>
std::string TC<short,char>::str="dadads";
偏特化
偏特化主要在于两方面:
- 模板参数数量上
cpp
template <typename T,typename C>
class TC {};
// 对部分模板参数进行特化
template <typename C>
class TC<double,C>
{};
- 模板参数权限范围上
cpp
template <typename T,typename C>
class TC {};
// 缩小权限范围
template <typename T,typename C>
class TC<const T,C>{};
template <typename T,typename C>
class TC<T*,C>{};
成员函数模板
类模板还是普通类,都可以有各自独立的成员函数模板。
类模板中的普通成员函数或是成员函数模板,只有其在源代码中被调用了,才会出现在实例化的类模板中。
编译器暂不支持虚函数成员模板,因为虚函数成员模板会导致该类的虚函数表大小无法确定。
cpp
class TC {
// 普通类中的成员函数模板
template <typename T>
void func(T t) {}
};
// 类模板中的成员函数模板
template <typename T>
class AC {
template <typename C>
void func();
};
// 类外定义类模板的成员函数模板
template<typename T> // 先写类模板的参数,排在上面
template<typename C> // 再写构造函数模板自己的模板参数
void AC<T>::func() {}
拷贝构造函数模板,赋值运算符重载函数模板
其本质上可认为是该类的拷贝构造函数,赋值运算符重载函数的一份重载,即拷贝构造函数模板永远不可能成为拷贝构造函数。
cpp
template <typename T>
class AC {
public:
AC<T>(){}
AC<T>(const AC<T>& _ac)
{
std::cout<<"call 拷贝构造\n";
}
AC<T>& operator=(const AC<T>& _ac)
{
std::cout<<"call 赋值运算符重载\n";
return *this;
}
template <typename C>
AC(const AC<C>& _ac)
{
std::cout<<"call 拷贝构造模板\n";
}
template <typename C>
AC<T>& operator=(const AC<C>& _ac)
{
std::cout<<"call 赋值运算符重载模板\n";
return *this;
}
};
int main() {
AC<int> ac{};
AC<int> ac1{ac}; // 调用拷贝构造
AC<int> ac2{AC<double>{}}; // 调用拷贝构造模版
ac1=AC<int>{}; // 调用赋值运算符重载
ac1=AC<char>{}; // 调用赋值运算符重载模版
return 0;
}
分离编译导致的模版实例化问题
CPP采用分离编译,即每个.cpp文件单独编译,在最后链接为一个可执行文件。
若模版的声明和定义在分别在两个文件中,如sub.h和sub.cpp:
cpp
// sub.h
template <class T>
T sub(T a,T b);
// sub.cpp
#include "sub.h"
template <class T>
T sub(T a,T b)
{
return a-b;
}
当main.cpp通过引用头文件调用函数模版:
cpp
// main.cpp
#include "sub.h"
int main()
{
sub<int>(1,2);
return 0;
}
sub.cpp和main.cpp分离编译,sub.cpp中并没有sub<T>的调用,也就不会进行实例化,也就没有生成sub<int>函数;链接时main.cpp无法找到对应的函数实例,则会出现链接错误。
故:对于模版,声明和定义请写在同一文件中!