Parametric Polymorphism (templates)

We only need to change the definition of ItemType and size in the following implementation of stack to transform it from a stack of 100 integers into a stack of 75 employee records:

// redefine as needed:
#define size 100
typedef int ItemType;

class Stack
{
public:
   Stack() { sp = 0; }
   ItemType top() const;
   void pop();
   void push(const ItemType& s);
   bool full() const { return sp == size; }
   bool empty() const { return sp == 0; }
private:
   ItemType items[size];
   int sp;
};

What do we do if we want to stack integers and employees in the same program? ItemType and size can't have two definitions. We'll have to duplicate the stack code:

class IntStack { ... };

class EmployeeStack { ... };

Templates

A template is a pattern for creating classes (and a class is a pattern for creating objects) or functions. A template looks like an ordinary class or function declaration, except it is preceded by the reserved word "template," followed by a list of parameter declarations:

template <PARAM, PARAM, ...>
class C { ... };

template <PARAM, PARAM, ...>
void f { ... }

Parameters can be value parameters such as numbers, pointers, and objects:

template<float x, Person* y, Employee e> DECLARATION

Parameters can also be type parameters:

template<typename T1, typename T2> DECLARATION

Equivalently:

template<class T1, class T2> DECLARATION

Of course a mixture of type and value parameters is also permissible.

Terminology

Functions and Procedures are parameterized block statements. (Functions are parameterized block statements that return values, procedure blocks do not return values.) The block statement isn't executed at the point where the function/procedure is declared, rather the block is executed when and where the function/procedure is called or invoked. Of course the function/procedure may be called in many places, and each call may be supplied with different arguments.

A generic is a parameterized declaration. The declaration isn't executed at the point where the generic is declared, rather the declaration is executed when and where the generic is instantiated (i.e., called). Of course the generic may be instantiated in many places, and each instantiation may be supplied with different arguments. Templates are the C++ implementation of generics.

Collectively, generics, procedures, and functions are called abstracts because they support the Abstraction Principle, which recommends separating the implementation (i.e. declaration) of a module from its use (i.e., invocation or instantiation).

Function Templates

We have already seen function templates:

template <typename Data>
void swap(Data& x, Data& y)
{
   Data temp = x;
   x = y;
   y = temp;
}

Recall that each time the compiler encounters a call to swap:

int a = 100, b = 200;
string u = "bat", v = "man";
// etc.
swap(a, b);
swap(u, v);

it infers the type, T, of its arguments, then generates a new function definition by replacing all occurrences of Data by T in the template. This is called instantiating the template. So for example, upon seeing the first call, the compiler generates the definition:

void swap(int& x, int& y)
{
  
int temp = x;
   x = y;
   y = temp;
}

The second call to swap causes the compiler to generate the definition:

void swap(string& x, string& y)
{
   string temp = x;
   x = y;
   y = temp;
}

We can think of the swap template as a generic algorithm for swapping the values of two variables. A generic algorithm is a type-independent algorithm. Swap is type-independent because it doesn't care about the types of its parameters, x and y, as long as they have the same type.

A Generic Stack

Class declarations can also be parameterized. Here's our solution to the reusable stack problem:

template <typename ItemType = int, int size = 100>
class Stack
{
public:
   Stack() {sp = 0;}
   ItemType top() const;
   void pop();
   void push(const ItemType& s);
   bool full() const { return sp == size; }
   bool empty() const { return sp == 0; }
private:
   ItemType items[size];
   int sp;
};

Here's how the push member function would be implemented. Notice that like swap, it is a template function:

template <typename ItemType, int size>
void Stack<ItemType, size>::push(const ItemType& s)
{
   if (size <= sp) error("Stack is full");
   items[sp++] = s;
}

Programmers create instances of a template by specifying a type argument in <> brackets:

Stack<int, 10> s1;
Stack<string> s2, s3;
s1.push(42);
s2.push("pancake");
// etc.

Note that the implementation and declaration of the stack template must be entirely contained in stack.h, which is then included in any file that instantiates the stack template. We can't separate the declaration of a template from its implementation. Remember, a template is a recipe used by the compiler to create class declarations. As such the entire recipe, declaration and implementation, must be included in each file where template instances are declared.