模板基本语法
模板参数:typename, class和具体类型
class和typename的作用一样,一般写成typename。调用时传一个具体的类型。
也可以在模板参数上写一个具体的类型,调用时需要传该类型的一个实例。
template<class C1, int, typename C2>
class C3;
C3<float, 5, double> c3;
模板的编译和实例化
参考:https://docs.microsoft.com/zh-cn/cpp/cpp/source-code-organization-cpp-templates?view=msvc-160
https://blog.csdn.net/flowshell/article/details/5999588
模板的实例化有显式指定、实参推演、显式实例化三种方式。
类模板实例化时必须显示指定类型,函数模板可以进行实参推演。
模板本身不对应任何代码,一个模板只有实例化的时候编译器才会生成代码。
惰性实例化:对于类模板,如果碰到一个新的实例化,编译器才会生成一个新的类。并且没使用过的模板类的成员函数(即函数模板)也不会被实例化。
实例化的时候编译器需要找到模板的声明和模板的定义。
The inclusion model(包含模型)
让实例化的cpp文件同时看到模板的声明和定义,在一个.h文件中同时包含类模板和函数的完整定义,或者同时包含分离的两个.h文件和.cpp文件。
编译慢,两个.cpp文件中有重复内容(编译的第一步是把.h文件插入cpp文件),但是灵活性高。
The explicit instantiation model(显式实例化模型)
模板的声明和定义分离,函数的定义写到cpp文件中,实例化的cpp文件不能同时看到模板的声明和定义。
如果不使用显式实例化,在两个cpp文件中编译器都无法完成模板的实例化(实例化的地方看不到模板的定义,模板的定义处看不到实例化参数)。
编译快(工作丢给链接器了),但是需要使用显式实例化,否则就会出现链接错误。
显式实例化没有办法惰性实例化模板类的成员函数(函数模板),所有成员函数都会被实例化。
// 显示实例化:在.cpp文件中指出要实例化的具体类型。
#include <iostream>
#include "MyArray.h"
using namespace std;
template<typename T, size_t N>
MyArray<T,N>::MyArray(){}
template<typename T, size_t N>
void MyArray<T,N>::Print()
{
for (const auto v : arr)
{
cout << v << "'";
}
cout << endl;
}
// 注意这里,指明了只实例化 double 和 string 两个类型
template MyArray<double, 5>;template MyArray<string, 5>;
类模板
参考:https://docs.microsoft.com/zh-cn/cpp/cpp/templates-cpp?view=msvc-160
https://www.runoob.com/w3cnote/c-templates-detail.html
有三种类型的模板形参:类型形参,非类型形参和默认模板形参。
类型模板形参:类型形参由关见字class或typename后接说明符构成。
模板的非类型形参也就是内置类型形参,如template class B{};其中int a就是非类型的模板形参。
带默认值的模板形参,应该放在最后面。
类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A m。(类声明时必须指示清楚参数是什么类型,如vecor,不存在vector<1>这种写法。)
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
template<class T1, class T2 = int>
class A {
public:
void h();
};
// 在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型,这里没有写T2=int
template<class T1, class T2>
void A<T1,T2>::h() {
}
函数模板
对函数模板的调用可以使用实参推演来进行,也可以显示指定模板形参的类型。
C98中函数模板不能指定默认值,从C++11开始函数模板也可以指定默认值。
当函数模板拥有多个默认模板参数时,其出现的顺序可以任意,不需要连续出现在模板参数的最后面。
类模板的成员函数都是函数模板。
没使用过的成员函数(即函数模板)不会被实例化。
模板特化
函数模板不允许偏特化,直接用函数重载即可
// 类模板
template <class T1, class T2>
class A{
T1 data1;
T2 data2;
};
// 全特化类模板
template <>
class A<int, double>{
int data1;
double data2;
};
// 偏特化类模板
template <class T2>
class A<int, T2>{
...
};
// 偏特化类模板,T1和T2必须包含const
template <class T1, class T2>
class A<T1 const, T2 const>{
T1 data1;
T2 data2;
};
SFINAE
Substitution failure is not an error
下面这段代码不会报错,因为int::foo适配错误不算错误,只要能找到一个可以实例化的版本就行。
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo) {} // Definition #1
template <typename T>
void f(T) {} // Definition #2
int main() {
f<Test>(10); // Call #1.
f<int>(10); // Call #2. Without error (even though there is no int::foo) thanks to SFINAE.
}
模板递归
template<int N>
struct Factorial
{
enum { Value = N * Factorial<N - 1>::Value };
};
template<>
struct Factorial<1>
{
enum { Value = 1 };
};
元函数
仿函数和元函数都是类,仿函数重载()运算符伪装成普通函数。
元函数是一个模板类,通过class_name