template(3.1)

Nontype Template Parameters非类型模板参数

对 function templates 和 class templates 而言,template parameters 并不一定非要是类型(types) 不可,它们也可以是常规的(一般的)数值。当你以类型(types)作为 template parameters时, 程序代码中尚未决定的是类型;当你以一般数值(non-types)作为 template parameter 时,程序代码中待定的内容便是某些数值。使用这种template时必须明确指定数值,程序代码才得以实例化。 本章将利用这个特性实作一个新版本的stack class template。此外我将举一个例子,展示如何在 function templates 中使用 nontype template paramaters,并讨论此技术的一些局限。

4.1 Nontype Class Template Parameters(非类型类别模板参数)

上一章实作了一个「元素个数可变」的stack class。与之对比,你也可以实作另一种stack,透过一个固定大小(fixed-size)的 array来容纳元素。这样做的好处是不必考虑诸如内存管理之类的问题。然而array大小的决定是一件比较困难的事:array愈小则stack愈容易满溢,array愈大则愈容易造成空间浪费。一个可行的解决办法是让使用者指定array大小,这个大小也就是stack 的最大元素个数。为了完成以上想法,我们应该把大小值当作一个 template parameter:

/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
#include <stdexcept>

template <typename T, int MAXSIZE>
class Stack {
  private:
    T elems[MAXSIZE];        // elements
    int numElems;            // current number of elements

  public:
    Stack();                  // constructor
    void push(T const&);      // push element
    void pop();               // pop element
    T top() const;            // return top element
    bool empty() const {      // return whether the stack is empty
        return numElems == 0;
    }
    bool full() const {       // return whether the stack is full
        return numElems == MAXSIZE;
    }
};

// constructor
template <typename T, int MAXSIZE>
Stack<T,MAXSIZE>::Stack ()
  : numElems(0)               // start with no elements
{
    // nothing else to do
}

template <typename T, int MAXSIZE>
void Stack<T,MAXSIZE>::push (T const& elem)
{
    if (numElems == MAXSIZE) {
        throw std::out_of_range("Stack<>::push(): stack is full");
    }
    elems[numElems] = elem;   // append element
    ++numElems;               // increment number of elements
}

template<typename T, int MAXSIZE>
void Stack<T,MAXSIZE>::pop ()
{
    if (numElems <= 0) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    --numElems;               // decrement number of elements
}

template <typename T, int MAXSIZE>
T Stack<T,MAXSIZE>::top () const
{
    if (numElems <= 0) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems[numElems-1];  // return last element
}

新加入的第二个 template parameter MAXSIZE 隶属 int 类型,用来指定「容纳 stack 元素」的那个底部 array 的大小:

template <typename T, int MAXSIZE>
class Stack {
private:
T elems[MAXSIZE]; // 元素
...
};
push()便是使用 MAXSIZE 来检查 stack 是否已满:
template <typename T, int MAXSIZE>
void Stack<T,MAXSIZE>::push (T const& elem)
{
if (numElems == MAXSIZE) {
throw std::out_of_range("Stack<>::push(): stack is full.");
}
elems[numElems] = elem; // 追加
++numElems; // 元素总数加 1
}

使用上述 class template 时,必须同时指定 (1) 元素类型和 (2) stack 元素的最大数量:

/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
#include <iostream>
#include <string>
#include <cstdlib>
#include "stack4.hpp"

int main()
{
    try {
        Stack<int,20>         int20Stack;     // stack of up to 20 ints
        Stack<int,40>         int40Stack;     // stack of up to 40 ints
        Stack<std::string,40> stringStack;    // stack of up to 40 strings

        // manipulate stack of up to 20 ints
        int20Stack.push(7);
        std::cout << int20Stack.top() << std::endl;
        int20Stack.pop();

        // manipulate stack of up to 40 strings
        stringStack.push("hello");
        std::cout << stringStack.top() << std::endl; 
        stringStack.pop();
        stringStack.pop();
    }
    catch (std::exception const& ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
        return EXIT_FAILURE;  // exit program with ERROR status
    }
}
注意,每一个被实例化(instantiated)的 class template 都有各自的类型。(译注:常见的误会是:上述三个 stacks 隶属同一类型。这是错误观念。)因此 int20Stack 和 int40Stack 是两个不同类型,不能互相进行隐式或显式转换,两者不能换用(彼此取代),也不能互相赋值。
你可以指定 non-type template parameters 的默认值:
template <typename T = int, int MAXSIZE = 100>
class Stack {
...
};

然而从设计角度来看,这样做并不恰当。Template parameters 的默认值应该符合大多数情况下的要求,然而把 int 当做预设元素类型,或指定 stack 最多有 100 个元素,并不符合一个「通用型 stack」的需求。更好的作法是让使用者指定这两个参数的值,并在文件中说明它们的意义。


4.2 Nontype Function Template Parameters(非类型函数模板参数)


你也可以为 function template 定义 nontype parameters。例如下面的 function template 定义了一组 函数,可以将参数 x 累加一个值(VAL)后传回:

// basics/addval.hpp


/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
template <typename T, int VAL>
T addValue (T const& x)
{
    return x + VAL;
}
当我们需要把「函数」或「某种通用操作」作为参数传递时,这一类函数就很有用。例如使用STL(Standard Template Library,标准模板库)时,你可以运用上述 function template 的实例(instantiation),将某值加到元素集内的每一个元素身上:

// (1)
std::transform (source.begin(), source.end(), // 来源端起止位置
dest.begin(), // 目的端起始位置
addValue<int,5>); // 实际操作

c++官网上的transform示例:
// transform algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::transform
#include <vector>       // std::vector
#include <functional>   // std::plus
int op_increase (int i) { return ++i; }
int main () {
std::vector
<int> foo; std::vector<int> bar; // set some values: for (int i=1; i<6; i++) foo.push_back (i*10); // foo: 10 20 30 40 50 bar.resize(foo.size()); // allocate space std::transform (foo.begin(), foo.end(), bar.begin(), op_increase); // bar: 11 21 31 41 51 // std::plus adds together its two arguments: std::transform (foo.begin(), foo.end(), bar.begin(), foo.begin(), std::plus<int>()); // foo: 21 41 61 81 101 std::cout << "foo contains:"; for (std::vector<int>::iterator it=foo.begin(); it!=foo.end(); ++it) std::cout << ' ' << *it; std::cout << ' '; return 0; }

最后一个自变量将 function template addValue()实例化了,使其操作成为「将5加进一个int数值中」。算法transform()会对source 中的所有元素调用这个具现体(函数),然后把结果传入dest中。注意上述例子带来的一个问题:addValue<int,5> 是个 function template 实体(instance),而我们知道,所谓「function templates 实体」被认为是命名了一组重载函数集,即使该函数集内可能只有一个函数。根据目前标准,编译器无法借助「重载函数集」来进行 template parameter的推导。因此你不得不把 function template argument 强制转型为精确类型:

// (2)
std::transform (source.begin(), source.end(), // 来源端起止位置
dest.begin(), // 目的端起始位置
(int(*)(int const*)) addValue<int,5>); // 操作
C++ Standard 中 已 有一 个提案 要 求修正这种行为 ,使你不必 在这种场合强制 转型(请参 考 [CoreIssue115])。在尚未获得修正之前,为保证程序的可移植性,你还是得像上面那么做。
 4.3 Nontype Template Parameters 的局限
注意,nontype template parameters 有某些局限:通常来说它们只能是常数整数(constant integral values),包括 enum,或是「指向外部链接(external linkage)之对象」的指针。以浮点数或 class-type objects 作为 nontype template parameters 是不可以的:

template <double VAT> // 错误:浮点值不能作为 template parameters
double process (double v)
{
    return v * VAT;
}

template <std::string name> // 错误:class objects 不能作为 template parameters
class MyClass {
    ...
};

不允许浮点字面常数(floating-point literals)或简单的常量浮点表达式(constant floating-point expressions)作为 template arguments,其实只是历史因素,并非技术原因。由于并没有什么实作上的困难,或许将来C++会支持它,请参考 13.4 节。由于字符串字面常数(string literal)是一种采用内部链接(internal linkage)的对象,也就是说不同模块(modules)内的两个同值的字符串字面常数,其实是不同的对象,因此它们也不能被拿来作为 template arguments:

template <char const* name>
class MyClass {
...
};
MyClass<"hello"> x; // 错误:不能使用字符串常量"hello"

此外,全局指针也不能被拿来作为 template arguments:
template <char const* name>
Class MyClass {
...
};
char const* s = "hello";
MyClass<s> x; // 错误:s 是「指向内部链接(internal linkage)对象」的指针
但是你可以这么写:
template <char const* name>
Class MyClass {
...
};
extern char const s[] = "hello";
MyClass<s> x; // OK
全局的 char array s 被初始化为 "hello",因此 s 是一个外部链接(external linkage)对象。8.3.3节, 13.4 节则讨论了这个问题未来的可能变化。

4.4 摘要

Templates parameters 不限只能是类型(types),也可以是数值(values)。你不能把浮点数、class-type 对象、内部链接(internal linkage)对象(例如字符串字面常数) 当作 nontype template parameters 的自变量。

原文地址:https://www.cnblogs.com/jianfengyun/p/3720710.html