前面介绍了类模板和函数模板,它们的模板参数可以是类型,也可以是非类型。其实模板参数还可以是第三种形式,即这个模板参数是一个类模板。不过这个仅对类模板适用,函数模板不支持。它的正式名字叫模板的模板参数(template template parameters),是不是听起来有点绕嘴?
看个例子,还是熟悉的Stack类模板
template<typename T>
class Stack {
T elems[100];
};
Stack使用数组作为内部存储。这个可能对用户来说是个限制,我们可以让用户来指定数据容器。
template<typename T, typename Cont = std::vector<T>>
class Stack {
Cont elems;
};
这次我们添加了第二个模板参数,其缺省值为std::vector<T>。不过这个定义感觉有点不太准确,因为第二个模板参数的类型其实是有限定的,虽然我们用了Cont来告诉使用者需要使用一个容器类,但这个其实并没有任何约束力,你可以用任何类型来实例化Cont,当然编译是通不过的。我们改用模板的模板参数来强调这个约束。
template<typename T, template<typename ElemType> class Cont = std::vector>
class Stack {
Cont<T> elems;
};
注意一下这次的定义和上面的区别,这次Cont不仅仅是个类型,它必须是个类模板,是有一个模板参数的类模板,这次Cont缺省值是std::vector。其实Cont里面的ElemType这个模板参数名称没有用处,可以省略,不过给它一个可读性强的名称会起来很好的说明作用。
不好意思,其实上面这个定义并不准确,在C++17之前甚至还是错误的,编译通不过。原因是std::vector这个类模板的模板参数是两个(虽然第二个模板参数定义了缺省值),上面的定义中第二个模板参数是有着一个模板参数的类模板(C++17之后编译器会考虑缺省的模板参数,编译可以通过了)。
template<
class T,
class Allocator = std::allocator<T>
> class vector
我觉得模板的模板参数最大的用途是用在“类型函数”的定义中。什么是类型函数?我们把之前的函数称为值函数:函数的输入参数是值,函数的返回结果也是值。换个角度来看类模板,它可以称为类型函数:模板参数是类型,通过定义类型成员来作为返回结果。
template<typename T>
struct AddRef{
using Type = T&;
};
template<typename T>
struct AddRef<T&>{
using Type = T&;
};
template<>
struct AddRef<void>{
using Type = void;
};
我们定义了一个类型函数(类模板),对于模板参数T,其返回类型Type为T&。后面我们定义了一个部分特化(处理模板参数本身是引用的类型,我们让它返回类型本身。我可不想应付引用的引用这一让人头大的东西)和一个完全特化(void类型可没法引用哦)。
好了,开始进入我们的重点。如果函数的参数类型中也有函数的话,我们称它为高阶函数。那类模板的模板参数本身也是个类模板的话,可以称为“高阶类型函数”。
template<typename T, template<typename> class TypeTrans>
struct Transform{
using Type = typename TypeTrans<T>::Type;
};
Transform类模板的功能也是给模板参数T返回一个转换后的类型,但是这次的转换方式是由第二个模板参数TypeTrans来完成的。这样将来如果你要给T添加指针,你只需要定义一个AddPointer类模板,然后用作它的第二个参数就行了。