Home

 

 

C++ Templates

 

A printable version of this web page can be found at

http://www.coquitlamcollege.com/adawson/TutorialTemplates.doc

 

 

 

Please note that in this document I am referring to code which is tested on

Borland C++ compiler version 5.02 (the current installation at Coquitlam College).

There are numerous C++ compilers and integrated development environments (IDEs)

currently in use, and your instructor is not an expert on all of them.  Please feel free to

email me questions on the Borland v5.02 compiler: adawson@coquitlamcollege.com

 

Introduction to Templates

 

In this tutorial, I am summarizing the material of Chapter 3 of the Weiss textbook.

In order to understand the material of this tutorial you will need to read

Chapters 1 and 2 of the textbook first.

 

An important goal of object-oriented programming is to reuse code.

C++ provides several different ways to reuse code, and templates is one way.

Inheritance is another way to reuse code - inheritance is the topic of Chapter 4.

There are different kinds of templates: the function template and the class template.

The C++ language comes with a library of ready-made templates -

the Standard Template Library (STL) which is the topic of Chapter 7. 

In this document you will see examples of the use of function templates,

class templates and templates from the STL.  This document is a work in progress.

As more examples become available, I will include them here.

 

A function template

 

Imagine you want to write a function to swap two whole numbers, say 5 and 6.

You could send those two integers to a function and the function swaps them.

You would send the two numbers in variables as arguments to a function.  If the

variables are received into reference parameters (those prefixed with the

ampersand symbol &), then if any changes are made to the variables, these are

permanent changes as we are working on the variables themselves and not on copies. 

 

The function would look like this:

 

void SwapInts( int & lhs, int & rhs )

{

    int tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

 

you would call it like this:  SwapInts(x,y);

 

 

 

Now imagine you want a function to swap real numbers (ones with a fractional part),

you would have to write a version of the Swap function for data of type double:

 

void SwapDoubles( double & lhs, double & rhs )

{

    int tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

Now imagine you want to swap to long integers, you'd have to rewrite the function like this:

 

void SwapLongs( long & lhs, long & rhs )

{

    int tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

 

These three functions are essentially doing the same thing - swapping the two values sent to them.

It would be nice if we could have a generic function, one which we could use for swapping pairs

of ints or pairs of doubles or pairs of longs. We'd like something like this:

 

void Swap( AnyDataType & lhs, AnyDataType & rhs )

{

    AnyDataType tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

where AnyDataType is a placeholder for the actual data type (int, double or long).

Function templates allow us to do this. 

 

The function template looks like this in a C++ program:

 

template <class Object>

void Swap( Object & lhs, Object & rhs )

{

    Object tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

The word "Object" stands for any data type. You could use any word (as long as it is

a valid identifier) in place of the word Object.  A function template is not an actual function.

A function template is a design or a pattern  for what could become a function.  In that

respect it is a bit like the difference between a data type and a variable of the data type.

A variable is an instance of a data type.  A function is an instance of a function template.

If a function template exists with the name "Swap", then when Swap is called with two

int variables, a version of the function is created, with the Object replaced with int:

 

void Swap( int & lhs, int & rhs )

{

    int tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

If Swap is called with two double variables, then an instance of the function template for

doubles is created:

 

void Swap( double & lhs, double & rhs )

{

    double tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

The primitive data types (short int, int, double etc.) are abstract data types which have a range

of allowable values and a set of operations which can be performed on those values. E.g. the

short int data type stores integers in the range  -32,767 to +32,767 and you can perform additions,

subtractions, multiplications etc. on them.  You can assignment an integer variable to another

integer variable.

 

If you tried to use the Swap function with objects of a class type, remember that the

assignment operator works differently with classes.  The copy assignment operator (operator =),

is used to copy objects.  To use the = with objects, both of the objects have to have previously

been constructed.  Thus for objects of a class type, the statement doesn’t work:

 

Object tmp = lhs;

 

For the line above to work with objects of a class type you would have to have a copy constructor

written in the definition of the class. If any of the member variables are pointers, extra care is needed.

With primitive types (and some built-in types like the String class) these details have been taken care

of, and you can use operators like =, +, - , >, < etc without having to define them yourself. 

 

The following code is FuncTemplates.cpp from: http://www.cs.fiu.edu/~weiss/adspc++2/code/

(scroll down the web page to see the code for Chapter 3).  A cut-down version of the same code

is shown on page 99 of the textbook - figures 3.2 and 3.3.

 

Note: I changed the first two lines so that the code compiles on Borland C++ version 5.02.

 

// Start of FuncTemplates.cpp

 

#include <iostream.h> // change to textbook code

// using namespace std; // change to textbook code

 

// Note: There are swap and min functions already defined in the library.

// Visual C++ has a bug that makes them visible.

// So to avoid this, we define Swap and Min (note capitalization).

 

// Swap function template

// Object: must have copy constructor and operator =

template <class Object>

void Swap( Object & lhs, Object & rhs )

{

    Object tmp = lhs;

    lhs = rhs;

    rhs = tmp;

}

 

// Min function templates

// Comparable: must have copy constructor and operator < 

template <class Comparable>

Comparable Min( const Comparable & lhs, const Comparable & rhs )

{

    if( lhs < rhs )

        return lhs;

    else

        return rhs;

}

 

// Exercise the function templates

int main( )

{

    int x = 5;

    int y = 7;

    double a = 2;

    double b = 4;

 

    Swap( x, y );   // Instantiates swap with int

    Swap( x, y );   // Uses already instantiated swap with int

    Swap( a, b );   // Instantiates swap with double

    cout << x << " " << y << endl;

    cout << a << " " << b << endl;

//  Swap( x, b );   // Illegal: no match

 

    cout << "Min of x and y " << Min( x, y ) << endl;

    cout << "Min of a and b " << Min( a, b ) << endl;

//  cout << "Min of a and x " << Min( a, x ) << endl;  // Illegal: ambiguous

 

    return 0;

}

 

// End of FuncTemplates.cpp

 

 

Note:  this is how I set up my C++ projects:

 

 

 

This is the code running:

 

 

 

 

 

Good programming practice requires placing a comment before a template with a list of the

conditions which must be satisfied by the template parameter.  Throughout the C++ textbook,

the author uses Object and Comparable as template types. (Any (legal) name could have

been chosen for the template type, but Object and Comparable are appropriate names,

and nicely but loosely compare to the Java's Comparable interface and Object class, which

form part of the Java language.)  For the Object template type it is assumed that a zero-parameter

constructor, a copy constructor and a copy assignment operator are available.  For the

Comparable template type it is assumed that a zero-parameter constructor, a copy constructor,

a copy assignment operator and the operator < are available.

 

Class templates

 

Class templates have the same advantages as function templates - you can write a definition

of the class template, then when you create an object using the template, you specify the

data type you wish to use with it.  A class template is created using the template keyword,

which tells the compiler that the following class definition will manipulate one or more unspecified

types.  At the time the object is defined, those type must be specified so the compiler can do

the substitution.

 

A class without a class template

 

First of all, here is an example of a Queue class implemented with a linked list of structures

which does NOT use a class template.  Later I will show you the same program amended

so it does use a class template.

 

This example is composed of three files:

 

QueueDriver.cpp (the code that tests the class)

QueueLi.h (the class definition)

QueueLi.cpp (the class implementation)

 

Copy and paste the following three files to the path shown in the following window,

and set up the project as shown in the same window. 

 

 

 

 

 

 

//*****************************************************

 

// queuedriver.cpp

#include "QueueLi.h"

#include <iostream.h>

#include <stdlib.h>

#include <conio.h>

 

 

int main ( )

{

   Queue line;

   char car;

   cout << "First car arrives at the pump"<<"\n";

   line.enqueue('A');

       cout << "Second car arrives at the pump"<<"\n";

   line.enqueue('B');

   cout << "Another car arrives at the pump"<<"\n";

   line.enqueue('C');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('D');

   cout << "A car arrives at the pump"<< "\n";

   line.enqueue('E');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('F');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('G');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('H');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('I');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

 

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

 

   cout << "Another car arrives at the pump" << "\n";

   line.enqueue('J');

   getch();

 

return 0 ;

}

// end off queuedriver.cpp

 

 

//******************************************

// queueli.h

 

#ifndef _QUEUELI_H_

#define _QUEUELI_H_

 

#include <stdlib.h>

#include "Except.h"

 

// Queue class -- linked list implementation.

//

// CONSTRUCTION: with no parameters.

//

// ******************PUBLIC OPERATIONS*********************

// void enqueue( x )  --> Insert x

// void dequeue( )    --> Return and remove least recent item

// Object getFront( ) --> Return least recently inserted item

// bool isEmpty( )    --> Return true if empty; else false

// void makeEmpty( )  --> Remove all items

//     int sizeOfQueue( ) --> Returns number of items in the Queue

// ******************ERRORS********************************

// UnderflowException thrown as needed.

 

 

class Queue

{

  public:

    Queue( );

    Queue( const Queue & rhs );

    ~Queue( );

    const Queue & operator= ( const Queue & rhs );

 

    bool isEmpty( ) const;

    const char & getFront( ) const;

 

    void makeEmpty( );

    char dequeue( );

    void enqueue( const char & x );

    int sizeOfQueue( );

 

  private:

    struct ListNode

    {

        char         element;

        ListNode     *next;

 

        ListNode( const char & theElement, ListNode * n = NULL )

          : element( theElement ), next( n ) { }

    };

    int                    size;

    ListNode *front;

    ListNode *back;

};

 

#include "QueueLi.cpp"

#endif

 

// end of queueli.h

//******************************************

 

//****************************************

// start of queueli.cpp

 

#include "QueueLi.h"

#include <iostream.h>

 

// default constructor

Queue::Queue( )

{

    front = back = NULL;

    size = 0;

}

 

 

// Copy constructor.

Queue::Queue( const Queue & rhs )

{

    front = back = NULL;

    *this = rhs;

}

 

// Destructor.

Queue::~Queue( )

{

    makeEmpty( );

}

 

// Test if the queue is logically empty.

// Return true if empty, false, otherwise.

bool Queue::isEmpty( ) const

{

    return front == NULL;

}

 

// Make the queue logically empty.

void Queue::makeEmpty( )

{

    while( !isEmpty( ) )

        dequeue( );

}

 

// Return the least recently inserted item in the queue

const char & Queue::getFront( ) const

{

    return front->element;

}

 

// Return and remove the least recently inserted item from

// the queue. Throw UnderflowException if empty.

char Queue::dequeue( )

{

    char frontItem = getFront( );

 

    ListNode *old = front;

    front = front->next;

    size--;

    delete old;

    return frontItem;

}

 

// Insert x into the queue.

void Queue::enqueue( const char & x )

{

       if (size < 15)

   {

       if( isEmpty( ) )

        back = front = new ListNode( x );

       else

        back = back->next = new ListNode( x );

      size++;

   }

   else

   {

       cout << "There are currently 15 cars at the pump. ";

      cout << "The car is turned away" << "\n";

   }

}

 

// Returns number of items in the Queue

int Queue::sizeOfQueue( )

{

       return size;

}

 

// Deep copy.

const Queue & Queue::operator=( const Queue & rhs )

{

    if( this != &rhs )

    {

        makeEmpty( );

        ListNode *rptr;

        for( rptr = rhs.front; rptr != NULL; rptr = rptr->next )

            enqueue( rptr->element );

    }

    return *this;

}

 

// end of queueli.cpp

//****************************************

 

 

When you compile, link and execute the code, you will see the following window:

 

 

 

 

 

 

Study the code and compare with the output.  In the following section of this document, you

will see the same code, but amended to use class templates.  The programs perform the same

actions.  You may choose to implement your code with or without templates. The code will

be a little simpler if you do not use templates, but knowing how to use them, and when to use

them, and recognizing what they look like and how they work is very important to any advanced

studies of data structures using C++.

 

A class defined as a class template

 

This example is composed of three files:

 

QueueDriverT.cpp (the code that tests the class)

QueueLiT.h (the class definition)

QueueLiT.cpp (the class implementation)

 

 

 

Copy and paste the following three files to the path shown in the following window, and

set up the project as shown in the same window. 

 

 

 

 

 

 

//**********************************************

// queuedrivert.cpp

// ***** denotes change from queuedriver.cpp code

#include "QueueLit.h"  // ***** contains class template

#include <iostream.h>

#include <stdlib.h>

#include <conio.h>

 

 

int main( )

{

   Queue<char> line; // ***** compare this statement to that in queuedriver.cpp:

   // Queue line;

   char car;

   cout << "First car arrives at the pump"<<"\n";

   line.enqueue('A');

       cout << "Second car arrives at the pump"<<"\n";

   line.enqueue('B');

   cout << "Another car arrives at the pump"<<"\n";

   line.enqueue('C');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('D');

   cout << "A car arrives at the pump"<< "\n";

   line.enqueue('E');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('F');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('G');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('H');

   cout << "A car arrives at the pump" << "\n";

   line.enqueue('I');

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

 

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

 

   cout << "Another car arrives at the pump" << "\n";

   line.enqueue('J');

   getch();

 

return 0 ;

}

// end of queuedrivert.cpp

 

//**********************************************

 

 

 

 

 

//***************************************************

// start of QueueLiT.h

#ifndef _QUEUELI_H_

#define _QUEUELI_H_

 

#include <stdlib.h>

#include "Except.h"

 

// Queue class -- linked list implementation.

//

// CONSTRUCTION: with no parameters.

//

// ******************PUBLIC OPERATIONS*********************

// void enqueue( x )  --> Insert x

// void dequeue( )    --> Return and remove least recent item

// Object getFront( ) --> Return least recently inserted item

// bool isEmpty( )    --> Return true if empty; else false

// void makeEmpty( )  --> Remove all items

//     int sizeOfQueue( )    --> Returns number of items in the Queue

// ******************ERRORS********************************

// UnderflowException thrown as needed.

 

template <class Object>

class Queue

{

  public:

    Queue( );

    Queue( const Queue & rhs );

    ~Queue( );

    const Queue & operator= ( const Queue & rhs );

 

    bool isEmpty( ) const;

    const Object & getFront( ) const;

 

    void makeEmpty( );

    Object dequeue( );

    void enqueue( const Object & x );

    int sizeOfQueue( );

 

  private:

    struct ListNode

    {

        Object             element;

        ListNode     *next;

 

        ListNode( const Object & theElement, ListNode * n = NULL )

          : element( theElement ), next( n ) { }

    };

    int                    size;

    ListNode *front;

    ListNode *back;

};

 

#include "QueueLit.cpp"

#endif

// end of QueueLiT.h

//***************************************************

 

 

 

 

 

//****************************************

// start of QueueLiT.cpp

 

#include "QueueLit.h"

 

 

// Construct the queue.

template <class Object>

Queue<Object>::Queue( )

{

    front = back = NULL;

    size=0;

}

 

// Copy constructor.

template <class Object>

Queue<Object>::Queue( const Queue<Object> & rhs )

{

    front = back = NULL;

    *this = rhs;

}

 

// Destructor.

template <class Object>

Queue<Object>::~Queue( )

{

    makeEmpty( );

}

 

// Test if the queue is logically empty.

// Return true if empty, false, otherwise.

template <class Object>

bool Queue<Object>::isEmpty( ) const

{

    return front == NULL;

}

 

// Make the queue logically empty.

template <class Object>

void Queue<Object>::makeEmpty( )

{

    while( !isEmpty( ) )

        dequeue( );

}

 

// Return the least recently inserted item in the queue

template <class Object>

const Object & Queue<Object>::getFront( ) const

{

    return front->element;

}

 

// Return and remove the least recently inserted item from

// the queue. Throw UnderflowException if empty.

template <class Object>

Object Queue<Object>::dequeue( )

{

    Object frontItem = getFront( );

 

    ListNode *old = front;

    front = front->next;

    size--;

    delete old;

 

    return frontItem;

 

}

 

// Insert x into the queue.

template <class Object>

void Queue<Object>::enqueue( const Object & x )

{

       if (size <15)

   {

       if( isEmpty( ) )

        back = front = new ListNode( x );

       else

        back = back->next = new ListNode( x );

      size++;

   }

   else

       cout<<" There are currently 15 cars at the pump, can not add anymore, The car is turned away"<<"\n";

}

// Returns number of items in the Queue

template <class Object>

int Queue<Object>::sizeOfQueue( )

{

       return size;

}

 

// Deep copy.

template <class Object>

const Queue<Object> & Queue<Object>::operator=( const Queue<Object> & rhs )

{

    if( this != &rhs )

    {

        makeEmpty( );

        ListNode *rptr;

        for( rptr = rhs.front; rptr != NULL; rptr = rptr->next )

            enqueue( rptr->element );

    }

    return *this;

}

 

//****************************************

// end of QueueLiT.cpp

 

When you compile, link and execute the code, you will see the following window:

 

 

 

 

 

 

 

Notice that the output is exactly the same as the output of the previous program which

does not use a class template.

 

Look at the code of  QueueLiT.h which contains the class definition.  The class definition

starts with the statement:

 

template <class Object>

 

This statement tells the compiler that the following class definition is a class template definition.

 

Compare the code of QueueLiT.h  with that of QueueLi.h .  The differences are the

statement: template <class Object> placed before the class definition, and the word

Object used in place of the data type char.  And that's it. 

 

 

 

The class template defined in QueueLiT.h is more flexible than the class definition of

QueueLi.h because it can be used with any data type, not just char.  Later I will show

you an example program using the same class template definition (QueueLiT.h ), and

same class template implementation (QueueLiT.cpp), but using int data instead of char

in the driver program.

 

 

 

Compare the method implementation code of QueueLiT.cpp  with that of QueueLi.cpp.

The differences are the statement: template <class Object> placed before each

class method definition, Queue<Object>:: used instead of Queue:: and the word

Object used in place of the data type char.  And that's it.

 

 

 

Now let's try using a different driver program (QueueDriverT2.cpp),

but use the same class template files: QueueLiT.cpp and QueueLiT.h.

 

 

 

 

 

// queuedrivert2.cpp

// ***** denotes change from queuedrivert.cpp code

#include "QueueLit.h"

#include <iostream.h>

#include <stdlib.h>

#include <conio.h>

 

 

int main( )

{

   Queue<int> line; // *****

   int car; // *****

   cout << "First car arrives at the pump"<<"\n";

   line.enqueue(1); // *****

       cout << "Second car arrives at the pump"<<"\n";

   line.enqueue(2); // *****

   cout << "Another car arrives at the pump"<<"\n";

   line.enqueue(3); // *****

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

   cout << "A car arrives at the pump" << "\n";

   line.enqueue(4); // *****

   cout << "A car arrives at the pump"<< "\n";

   line.enqueue(5); // *****

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

   cout << "A car arrives at the pump" << "\n";

   line.enqueue(6); // *****

   cout << "A car arrives at the pump" << "\n";

   line.enqueue(7); // *****

   cout << "A car arrives at the pump" << "\n";

   line.enqueue(8); // *****

   cout << "A car arrives at the pump" << "\n";

   line.enqueue(9); // *****

 

   cout << "First in line about to leave..." << "\n\n";

   car = line.dequeue( );

   cout << "Car " << car << " leaves" << "\n\n";

 

   cout << "Currently there are "<< line.sizeOfQueue() << " cars at the pump"<<"\n";

 

 

   cout << "Another car arrives at the pump" << "\n";

   line.enqueue(10);

   getch();

 

return 0 ;

}

// end of queuedrivert2.cpp

 

 

 

When you compile, link and execute the code, you will see the following window:

 

 

 

 

 

 

Note that the line:

 

Queue<int> line;

 

in QueueDriverT2.cpp allows you to create a queue of integers instead of a

queue of characters.

 

To create a queue of data type double (numbers with a fractional part) you would

simply substitute the above line with:

 

Queue<double> line;

 

 

 

 

 

 

 

This page is:  TutorialTemplates.htm, edited using: Word 2000

Last updated: Tuesday 6th July 2004, 8:48 PT by AHD