这部分的内容是从一个实际问题出发,从alias template(模板别名)引申到template template parameter(模板模板参数)。
一、Alias Template
一个模板别名的例子如下:
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
//我们就可以这样用
Vec<int> coll;
//这就等同于
std::vector<int, MyAlloc<int>> coll;
所以模板别名是可以接受参数的。使用宏和typedef均达不到模板别名这种效果。typedef不能接受参数,所以使用typedef,最多达到下面这种效果:
typedef std::vector<int, MyAlloc<int>> Vec;
这样的定义缺乏灵活性,并且定义的Vec也不是一个模板;使用宏的话出来的效果也不符合我们的要求:
#define Vec<T> template <typename T> std::vector<T, MyAlloc<T>>;
//使用的时候Vec<int>就会直接变为:
template <typename int> std::vector<int, MyAlloc<int>> //一个四不像
二、应用实例(引出模板模板参数)
考虑这样一种需求,假设我们需要实现一个函数test_moveable(容器对象,类型对象),从而能实现传入任意的容器和类型,都能将其组合为一个新的东西:容器<类型>,这样的话我们的函数应该怎么设计呢?
1. 解法一:函数模板(无法实现)
template <typename Container, typename T>
void test_moveable(Container cntr, T elem)
{
Container<T> c; //[Error] 'Container' is not a template
for(long i=0; i<SIZE; ++i)
c.insert(c.end(), T());
output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}
这样设计的思路是显而易见的,但很遗憾的是并不能通过编译,因为我们调用该函数传入的只能是对象,也就是假如我们传入一个list<int>的对象,那么Container就等同于list<int>,而list<int>并不是一个模板,我们希望改变list尖括号中的类型,但这样设计并不能做到。
2. 解法二:函数模板+iterator+traits(可以实现)
template<typename Container>
void test_moveable(Container c)
{
typedef typename iterator_traits<typename Container::iterator>::value_type Valtype;
for(long i=0; i<SIZE; ++i)
c.insert(c.end(), Valtype());
output_static_data(*(c.begin()));
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}
(注:这里还涉及到了typename的第二种用法,用于表示后面跟着的是一个类型,主要是为了避免歧义,因为作用域符号后面跟着的可能是类型,也可能是成员,具体见链接https://www.cnblogs.com/wuchanming/p/3765345.html)
这样做是可以达到效果的,但是却改变了函数签名,使用的时候我们需要这样调用:test_moveable(list<int>()),和我们开始设计的是不一样的。
那么,有没有template语法能够在模板接受一个template参数Container时,当Container本身又是一个class template,能取出Container的template参数?例如收到一个vector<string>,能够取出其元素类型string?那么这就引出了模板模板参数的概念。也就是下面的解法三。
3. 解法三:模板模板参数 + alias template(可以实现)
template <typename T,
template <typename T>
class Container
>
class XCls
{
private:
Container<T> c;
public:
XCLs()
{
for(long i=0; i<SIZE; ++i)
c.insert(c.end(), T());
output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}
};
(注:模板模板参数中的T可以不写,默认就是前面的T)
使用上面的定义,在实际使用时还是会报错:
XCls<MyString, vector> c1; //[Error] vector的实际类型和模板中的Container<T>类型不匹配
这是因为vector其实有两个模板参数,虽然第二个有默认值,我们平时也可以像vector<int>这样用。但是在模板中直接这样写是不匹配的。所以这里就用到了我们一开始提到的模板别名,只要传入的是vector的模板别名就可以了,如下所示:
//不得在function body之内声明
template<typename T>
using Vec = vector<T, allocator<T>>;
XCls<MyString, Vec> c1;
其中模板别名的定义不能在function body之内,也就是需要写在任何函数的外面,包括主函数。