对函数模板和类模板而言,模板参数并不一定非要是类型不可,它们也可以是常规数值。当你以类型(types)作为template parameters时,程序代码中尚未决定的是类型;当你以一般数值(non-types)为模板参数时,程序中代码中待定的内容便是某些数值。使用这种模板,也就是本文所说的非类型模板时,必须明确指定数值,程序代码才得以实例化。本文将用非类型模板作一个类模板,也将展示如何在函数模板中使用非类型模板参数。
1. 非类型类模板参数
前面文章中提到了一个可变大小的堆栈类,其实还可以实现一个固定大小的数组来容纳元素。这样的好处是不必考虑诸如内存管理之类的问题。然而array大小的决定是一个比较困难的事情:array越小,堆栈容易溢满;array越大则容易造成空间浪费。一个可行的办法是让使用者指定array大小。这个大小也就是Stack的最大元素个数。
为了完成以上想法,我们应该把大小值当作一个template parameter:
1) 可以把这个非类型模板类TemplateStack的声明与定义放在一个头文件中:
//nonetypetemplate.h
#ifndef NONETYPETEMPLATE_H
#define NONETYPETEMPLATE_H
#include <stdexcept>
template <typename T, int MAXSIZE>
class TemplateStack {
private:
T elements[MAXSIZE];
int numElements;
public:
TemplateStack();
void push(T const&);
void pop();
T top() const;
bool empty() const {
return numElements == 0 ;
}
};
//constructor
template <typename T, int MAXSIZE>
TemplateStack<T, MAXSIZE>::TemplateStack()
:numElements(0)
{
}
template <typename T, int MAXSIZE>
void TemplateStack<T,MAXSIZE>::push(T const& elem)
{
if(numElements == MAXSIZE){
throw std::out_of_range("TemplateStack<>::push:stack is full!");
}
elements[numElements] = elem;
++numElements;
}
template <typename T, int MAXSIZE>
void TemplateStack<T,MAXSIZE>::pop()
{
if(numElements <= 0){
throw std::out_of_range("TemplateStack<>::pop:stack is empty!");
}
--numElements;
}
template <typename T, int MAXSIZE>
T TemplateStack<T,MAXSIZE>::top() const
{
if(numElements <= 0){
throw std::out_of_range("TemplateStack<>::top:stack is empty!");
}
return elements[numElements - 1];
}
#endif // NONETYPETEMPLATE_H
2)使用这个模板类的文件放在主程序main.cpp中
//main.cpp
#include <QCoreApplication>
#include "nonetypetemplate.h"
#include <iostream>
#include <string>
#include <cstdlib>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
try {
TemplateStack<int, 20> int20Stack;
TemplateStack<int, 40> int40Stack;
TemplateStack<std::string, 40> string40Stack;
int20Stack.push(7);
std::cout << int20Stack.top() << std::endl;
int20Stack.pop();
string40Stack.push("hello,world");
std::cout << string40Stack.top() << std::endl;
string40Stack.pop();
string40Stack.pop();
} catch (std::exception const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return EXIT_FAILURE;
}
return a.exec();
}
3)运行结果
2. 非类型函数模板参数
也可以为函数模板定义非类型参数,例如下面的function template定义了一组函数,可以将参数x累加一个值VAL后传回:
template <typename T, int VAL>
T addValue(T const& x)
{
return x + VAL;
}
当我们需要把函数或某种通用操作作为参数传递时,这一类函数就很有用。例如使用STL时,可以运用上述function template的实例,将某值加到元素集内的每个元素上:
std::transform(source.begin(), source.end(), //来源端起止位置
dest.begin(), //目标位置
addValue<int,5>); //实际操作
最后一个自变量将函数模板addValue()实例化了,使其操作变成了加5.算法transform()会对 source中所有的元素调用这个具体现(函数),然后把结果传入dest中。
注意上面例子的一个问题:addValue<int, 5>是个函数模板(function Template)实体(instance),而所谓函数模板实体就是命名了一组重载函数集,即使该函数集内可能只有一个函数。根据目前标准,编译器无法借助重载函数集来进行模板参数的推导,不用我们把函数模板参数function template argument强制转型为精确类型(本例中第一个参数为整形,第二参数为常整形):
std::transform(source.begin(), source.end(),
dest.begin(),
(int(*), (int const*)) addValue<int, 5>);
)
3. 非类型模板参数的局限
非类型模板参数有某些局限:通常来说它们只能是常数整数(const intergral value), 包括enum, 或是指向外部链接(external linkage)对象的指针。
以浮动数或是class-type objects作为nontype template parameters是不可以的:
template <double VAT> //错误:浮动值不能作为template paramters
double process (double v)
{
return v *VAT;
}
template <std::string name> //错误,class objects不能作为模板参数
class MyClass{
...
}
不允许浮动常数或简单的常量浮点表达式作为模板参数,其实只是历史因素,并非技术原因,或许将来会支持;
字串常数是一种采用内部链接(internal linkage)的对象,也就是不同模块内的两个同值的字符串常数,其实是不同的对象,因此不能用来作模板参数template arguments:
template <char const* name>
class MyClass{
...
};
MyClass<"hello"> x; //error:不能使用字符串常量
此外,全局指针也不能用作模板参数template argument:
template <char const* name>
Class MyClass{
...
}
char const *s = "hello";
MyClass<s> x ;
但是可以这么写:
template<char const* name>
Class MyClass {
...
};
extern char consts[] = "hello";
MyClass<s> x; //OK
全局的字符数组s(char Array)被初始化为“hello”,因此,s是一个外部链接对象(external linkage)
4. 小结
Template parameters 不仅仅只能时类型types,也可以是数值(values)。
注意得是:不能把浮动数、class-type对象、内部链接in非类型模板参数nontype template parameters的自变量。