模板
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;
通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对仅数据成员和成员函数类型不同的类。
c++ 函数模板
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
函数体
}
其中template和class是关键字,class可以用typename 关见字代替,在模板种这里typename 和class没区别,这时历史原因造成的就不多说了。
.注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。
例子:
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
T 是一个模板参数; typename 关键字指出此参数是某个类型的占位符。 调用函数时,编译器会将的每个实例替换为 T 具体类型参数,该参数由用户指定或由编译器推断。 编译器从模板生成类或函数的过程称为 模板实例化; minimum 是模板的实例化 minimum 。
int a = 1;
int b = 2;
int i = minimum<int>(a, b);
//因为函数模板可以实参推演,所以模板后面可以不用指定类型。
int i = minimum(a, b);
当编译器遇到最后一个语句时,它会生成一个新函数,其中,模板中的每个 T 均替换为 int :
int minimum(const int& lhs, const int& rhs)
{
return lhs < rhs ? lhs : rhs;
}
类型参数
上面的例子就算类型参数,参数是某个类型。
任何内置或用户定义的类型都可用作类型参数,但是,这个类型里面的操作之类的要注意是否支持:
class MyClass
{
public:
int num;
std::wstring description;
};
int main()
{
MyClass mc1 {1, L"hello"};
MyClass mc2 {2, L"goodbye"};
auto result = minimum(mc1, mc2); // Error! C2678
}
//将生成编译器错误,因为 MyClass类型没有运算符的重载 < 。
非类型参数
非类型参数仅限于int、enmu、指针和引用
可以提供常量整数值来指定数组的长度,如此示例类似于标准库中的 std:: array 类:
template<typename T, size_t L>
class MyArray
{
T arr[L];
public:
MyArray() { ... }
};
//这里size_t是std::size_t,在 size_t 编译时,值作为模板参数传入,并且必须为 const 或 constexpr 表达式,即这个位置类型是固定了的std::size_t,
template<int Val, typename T>
T addValue (T x) {
return x + Val;
}
//这里模板参数Val是int,是固定了的,即类型不可以作为参数,只能是int类型的值,有点理解了么?
//而类型参数比如typename T,T是代表一种类型
使用 auto 推断非类型模板参数,也是非类型参数。
这个有点绕, 我们在编写模板的时候就必须明确非类型模板参数的具体类型,C++17 打破了这一限制,让我们能够在非类型模板参数中使用 auto 关键字,从而让编译器推导具体的类型:
//c++17以前,想要传入一个可以用户自己指定类型的非类型参数,必须这样定义,
//前面的typename Type用来给用户确定类型,后面的Type value用来确定非类型参数
template <typename Type, Type value> constexpr Type TConstant = value;
constexpr auto const MySuperConst = TConstant<int, 100>;
//c++17以后,我们可以不这么麻烦,交给编译器自己去确定类型:
template <auto value> constexpr auto TConstant = value;
constexpr auto const MySuperConst = TConstant <100>;
//在比如
template <auto value> void foo() {
return;
}
foo<10>();
//再比如
template <auto x> constexpr auto constant = x;
auto v1 = constant<5>; // v1 == 5, decltype(v1) is int
auto v2 = constant<true>; // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>; // v3 == 'a', decltype(v3) is char
以上非类型参数在类模板中本质上是一回事。
c++ 类模板
类模板写法
template<class 形参名,class 形参名,…> class 类名
{ ... };
对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a’ uses undefined class ‘A’),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A m。
类模板定义种函数定义在内部和外部是有区别的:
如果在类模板的外部定义成员函数,则会像定义函数模板一样定义它们:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
其中(类名<模板形参名>::函数名)是一个整体,就像平时类外定义函数一个意思,只是多了<模板形参名>。
例子:
#include <iostream>
using namespace std ;
template <class T>
class Base
{
public :
T a ;
Base(T b)
{
a = b ;
}
T getA(){ return a ;} //类内定义
void setA(T c);
};
template <class T> //模板在类外的定义
void Base<T>::setA(T c)
{
a = c ;
}
int main(void)
{
Base <int>b(4);
cout<<b.getA()<<endl;
Base <double> bc(4);
bc.setA(4.3);
cout<<bc.getA()<<endl;
system("pause");
return 0 ;
}
另外,类模板的成员函数可以是函数模板,并指定附加参数,如下面的示例所示。
// member_templates.cpp
template<typename T>
class X
{
public:
template<typename U>
void mf(const U &u);
};
template<typename T> template <typename U>
void X<T>::mf(const U &u)
{
}
int main()
{
}
哈哈哈套娃
下面来更套娃的
模板作为模板参数
template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
T t; //OK
Arr<T, 10> a;
U u; //Error. U not in scope
};
//由于 Arr 参数本身没有正文,因此不需要它的参数名称,它作为参数只是定义了传入参数的样子,
//或者具体说传入模板是什么样的。 事实上,引用 Arr 的 typename 或类参数名称是错误的 MyClass2 。
//出于此原因,可以省略 Arr 的类型参数名称,如以下示例中所示:
template<typename T, template<typename, int> class Arr>
class MyClass2
{
T t; //OK
Arr<T, 10> a;
};
//在声明模板模板参数的时候也要包括完整的模板声明,把 template<typename, int> class Arr看成一种类型就行。然后就可以使用这个参数类型了。
不过讲真,把模板作为参数,实例化这个模板时你得传入或者默认一个模板作为参数,就挺离谱。
或许可以看成一种指针,比较类似的定义类或者函数时,你要想传入函数作为参数,就得通过定义函数指针来实现。好处在于,模板作为参数,说明我把要用到这个模板的地方给参数化了,就是可以通过传参来实现更改模板使用。函数作为参数也是这种作用。只能说太他妈强大了。python中弱化了如何定义,你要想把函数之类的作为参数,直接传就行,,就算你得自己做好参数类型检查,,。
重用模板参数
class Y
{
};
template<class T, T* pT> class X1
{
};
template<class T1, class T2 = T1> class X2
{
};
Y aY;
X1<Y, &aY> x1;
X2<int> x2;
int main()
{
}
其他如
类模板专用化
其实就算指定特定类型用特定的模板。就是模板类名后面用<>去指定特定的类
当用户使用该类型对模板进行实例化时,编译器将使用特殊化来生成类,对于所有其他类型,编译器将选择更常规的模板。 专用化,其中所有参数 都是专用的。 如果仅某些参数是专用的,则称为 部分专用化。
template <typename K, typename V>
class MyMap{/*...*/};
// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization
只要每个专用类型参数都是唯一的,模板就可以拥有任意数量的专用化。 只有类模板可以部分专用化。 模板的所有完整和部分专用化必须在与原始模板相同的命名空间中声明。
类模板友元
类模板可以具有 友元。 类或类模板、函数或函数模板可以是模板类的友元。 友元也可以是类模板或函数模板的专用化,但不是部分专用化。
参考:
https://docs.microsoft.com/zh-cn/cpp/cpp/templates-cpp?view=msvc-160
https://glumes.com/post/c++/c+±template-5/