7.3 Variable and Constant Template Parameters


Template parameters may be variables or constant values, including a constant class name. When a variable is used as a template parameter, a value for the variable must be supplied when the template is elaborated. A template may also have both a class parameter and a variable parameter. A constant template parameter is used to define special-case templates, which are often used in conjunction with a general template to define a special variant of the general template. A special variant is often needed because there are a few cases in which the assumptions made by the general template are not appropriate and an alternative template is needed. Both of these kinds of template parameters are described in this section.


Variables as Template Parameters

A variable as a template parameter is shown in the following redefinition of the Queue template. Notice that the template has two parameters: QueueItem defines the type of the elements maintained by the queue, and size defines the maximum number of elements that can be in the queue at any one time.


Queue Template Class with a Variable Parameter
template <class QueueItem, int size> class Queue {
   private:
      QueueItem buffer[size];
      int head, tail, count;
    public:
                Queue();
      void      Insert(QueueItem item);
      QueueItem Remove(); 
      int       Size();
               ~Queue();
   };

  

Both template parameters are used in the declaration of the buffer array

     QueueItem buffer[size];

where QueueItem defines the base type of the array and size defines the length of the array. The size template parameter is used in the code for the template, as shown below. Notice that the full list of template arguments must be given at numerous places in the definition.


Implementation of the Revised Template Class
template <class QueueItem, int size>           
      Queue<QueueItem,size>::Queue() : count(0), head(0), tail(0) {}
   template <class QueueItem, int size> 
      void Queue<QueueItem, size>::Insert(QueueItem item) {
             assert(count <size);
             buffer[tail] = item;
             tail = (tail + 1)% size;
             count++;
      }
   template <class QueueItem, size> 
      QueueItem Queue<QueueItem, size>::Remove() {
            assert(count > 0);
            int val = head;
            head = (head + 1)%size;
            count--;
            return buffer[val];
      }
   template <class QueueItem, int size> 
      int Queue<QueueItem, size>::Size() { return count; }
   template <class QueueItem, int size>           
      Queue<QueueItem, size>::~Queue() {}

  

Notice that the size parameter is used whenever the array length is needed. Thus, instead of head=(head+1)%100; being used to adjust the index of the head of the queue, the code uses the template variable size to refer to the length of the buffer, as in head = (head + 1) % size;.

Queues of various types and size may be declared and used as follows:

       Queue<int,100>       smallIntegerQueue;
        Queue<int, 1000>     largeIntegerQueue;
        Queue<float, 100>    smallRealQueue;
        Queue<float, 1000>   largeRealQueue;

        smallIntegerQueue.Add(10);
        largeIntegerQueue.Add(20);
        smallRealQueue.Add(1.4159);
        largeRealQueue.Add(0.123);

The type of an object created via a template depends on all of the template's parameters. It was previously seen that instantiating the template with different types created objects of different types. Thus, using the previous declaration of the queue template:

       Queue<int>   intQueue1;
        Queue<int>   intQueue2;
        Queue<float> realQueue;

created objects of two different types. As usual, objects of the same type can be assigned to each other while objects of different types cannot. This is illustrated by the following code:

       intQueue1 = intQueue2;          // OK, queues of the same type

        intQueue1 = realQueue;          // ERROR - queues of incompatible type

        realQueue = intQueue2;          // ERROR - queues of incompatible type

The type compatiblilty of objects created by templates with multiple parameters is similar: two template elaborations are the same if and only if they are elaborated with the same parameters. This means that any class parameters must be the same and any variable parameters must have the same value. Here, these rules are illustrated using the modified (two parameter) Queue template:

       Queue<int,   100>   largeIntegerQueue1;
        Queue<int,   100>   largeIntegerQueue2;
        Queue<int,    10>   smallIntegerQueue;
        Queue<float, 100>   largeRealQueue;
     
        largeIntegerQueue1 = largeIntegerQueue2; // OK, queues of the same type

        largeIntegerQueue1 = largeRealQueue;     // ERROR - queues of incompatible type
        largeIntegerQueue1 = smallIntegerQueue;  // ERROR - queues of incompatible type

The first assignment is between compatible types - both the QueueItem type (int) and the size value (100) are the same. However, the second two assignments are in error. The first error occurs because the QueueItem parameters do not agree (int vs. float) although the size parameter is the same (100). The second error occurs because the size parameter is different (100 vs. 10), although the QueueItem parameter is the same.


Special-Case Templates

There may be certain classes that cannot be used to elaborate a general template because the class does not satisfy the assumptions made by the template about the class given as its parameter. It was seen earlier that the Displayable template made assumptions about its parameterizing class. A portion of the definition of the Displayable template is repeated below for reference.


Displayable Template Repeated
template <class T> class Displayable {
     private:
       T*       displayed;
       TextBox* textBox;
     public:
       Displayable(TextBox* tbox);        // testbox to show in
       void ShowThis(T* d);               // what to show
       void Show();                       // show current value
       void Reset();                      // reset displayed from textBox
       ~Displayable();
   };

   ...

   template <class T> 
      Displayable<T>::Show() { textBox->SetText(displayed->ToString()); }

   template <class T> 
      Displayable<T>::Reset() { displayed->FromString(textBox->GetText()); }

  

The important thing is that the Displayable template assumes that the instantiating class has methods ToString and FromString that convert between the class's internal representation of its value and an external string representation.

Certain obvious uses of the Displayable template will not work because the instantiating class (or type) does not have the methods required by the template. For example:

       Displayable<int>      intDisplay;
        Displayable<float>    realDisplay;
        Displayable<Location> locationDisplay;

will not work because none of these built-in types has the methods required by the Displayable template. While it might be possible to add the required methods to the Location class, it is not possible to do this for the built-in types int and float.

A "special case" version of the Displayable template can be defined for ints as shown below. Notice that the template parameter is a fixed (or constant) type: namely the built-in type int. This template provides the special-case definitions of the ToString and FromString methods that are needed by the template but which are lacking in the int type.


Special-Case Displayable Template
class Displayable<int> {
      private:
        int*       displayed;
        TextBox* textBox;
        char* ToString(int v);
        int   FromString(char* text);
      public:
        Displayable(TextBox* tbox);        // textbox to show in
        void ShowThis(int* d);             // what to show
        void Show();                       // show current value
        void Reset();                      // reset displayed from textBox
        ~Displayable();
   };


   char* Displayable<int>::ToString(int v) { 
      char* buf = new char[10];
      ostrstream format(buf);
      format << v;
      return buf;
   }

   int Displayable<int>::FromString(char* text) { 
       istrstream(text) format;
       int value;
       format >> value;
       return value; }

   Displayable<int>::Displayable(TextBox *tbox)
         {textBox = tbox;  displayed = (int*)0;}

   void Displayable<int>::ShowThis(int* d) { displayed = d; }

   void Displayable<int>::Show() { textBox->SetText(ToString(*displayed)); }

   void Displayable<int>::Reset() { *displayed = FromString(textBox->GetText()); }

   Displayable<int>::~Displayable(){}

  

  1. Extend the IntArray template developed in Exercise 8 in section 7.1 to include a template argument defining the length of the array.

  2. Define and implement a special-case template for Displayable<float>.

  3. Write a program that instantiates three IntArray objects, two of the same size and one of a different size. Show that you can assign and retrieve values at different array positions in each object.

  4. Using the three objects created above, try assigning each one to the other two. Which assignments are valid (will compile) and which produce error messages?

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

 

 

ÿ