下面是我们熟悉的类模板的例子:一个简单的容器栈,它可以支持不同的类型做元素。
#include <vector>
#include <stdexcept>
template<typename T>
struct Stack
{
void push(const T& elem)
{
elems.push_back(elem);
}
T pop()
{
if(empty()) throw std::out_of_range("Stack<>::pop: empty!");
auto elem = elems.back();
elems.pop_back();
return elem;
}
bool empty() const
{
return elems.empty();
}
private:
std::vector<T> elems;
};
它的用法如下:
Stack<int> intStack;
intStack.push(-1);
intStack.push(2);
intStack.push(-3);
std::cout << intStack.pop() << std::endl;
Stack<char> charStack;
charStack.push('A');
charStack.push('B');
std::cout << charStack.pop() << std::endl;
对于模板元编程,我们可以将类模板想象成一个编译期的函数,不同的是它的参数列表放在一对尖括号中。通过template<typename T> struct Stack
我们声明了一个编译期的函数,它的名字叫做Stack,它有一个类型形参T。
标准规定可以用typename或者class关键字指示模板形参是一个类型,不能使用struct。由于模板的类型形参不仅可以被替换为用户自定义类型,也可以被替换为内置类型(int, char, double...),所以使用typename语义上比class更清晰一些。本文统一使用typename。
如同把具体的实参传递给一个函数,函数就会计算求值一样,当我们把具体的类型当做实参传递给类模板时,类模板会在编译期进行计算,返回一个具体的类型。类模板的传参和函数类似,只不过语法上使用尖括号。
上例中我们分别将int和char当做实参,传递给类模板Stack。Stack得到实参后变成具体的类型Stack<int>
和Stack<char>
。根据运行期C++的要求,只有具体类型才能产生对象,所以我们分别用Stack<int>
和Stack<char>
生成了两个对象intStack
和charStack
。
类模板的实现中可以继续使用类模板。上例中Stack的实现中使用了标准库中的类模板std::vector
。一旦我们在Stack中用具体类型替换形参T,std::vector<T>
也会被传递参数从而变成一个具体类型,使得可以产生elems
对象。
上面虽然我们用编译期函数来类比类模板,但是这时的类模板还远未达到模板元编程的要求,我们继续!