Templates

- Many programming situations require the same class design for multiple classes
- The only difference might be the data type being implemented
- Consider the data structure classes studied in the notes on stacks and queues
- What if we need, for example, a stack of floats rather than a stack of ints?
- Consider other classes such as linked lists or arrays, or even functions
- What if the basic implementation needs to be the same, but the types used are different?
- C++ templates are used to avoid repetitive code but apply across different types

 

Definition
- C++ Templates are a facility to provide abstraction for classes and functions across different types
- Templates were a feature added after the initial development of C++, in 1983, at AT&T Bell labs
- Templates are one of the most powerful features in C++ providing reuse capability
- Template is a parameterized class type using the notion of variable types as parameters
- The parameter (something that varies) in a template is the variable type used in the implementation
- Type can be a parameter in the definition of a class or a function
- Parameters specify customization of a "generic" template to create specific functions/classes

- Uses special syntax to denote the parameters used in the templates
- Templates substitute types at compile-time and do not incur any run-time overhead

 

Function templates
- Individual functions/methods can be constructed as a template
- The implementation is written while considering the type used as the parameter

- Consider a simple example of writing a function to print the values of an array

- The array could be of any variable type, but the way in which they are printed is the same

- A template to implement such a function might look like the following

 
#include <iostream>

using namespace std;

 

// Function template

template< class T >

void printArray( T *array, int count )

{

  for( int i = 0; i < count; ++i )

    cout << array[i] << " ";

  cout << endl;

}

 

- Note the special syntax to denote a template definition and the parameter type

 

template <class T>
 - Specifies that a template is being declared
 - Specifies that a parameter type argument named T is to be used

 

- Instead of defining a specific type (int, float), we substitute the template parameter T

- Note that we can name our parameter anything, doesn't have to be named T

- We use T as we would any other specific variable type

- For example, we define a “pointer to T” named array as the first function argument

 

void printArray( T *array, int count )

 

- Function templates can replace overloaded functions that differ only in the type used
- Function templates are used in applications in a similar manner

- The same function template is called using different types as in the example below

- At compile time, the parameter type T is substituted for the actual type used in the program

 

main()

{

  int ival[3] = { 0, 1, 2 };

  char cval[6] = { 'c', 's', 'c', '1', '2', '5' };

 

  printArray( ival, 3 );

  printArray( cval, 6 );

}

 

Output

0 1 2

c s c 1 2 5

 

  

 

Class templates
- C++ classes can be constructed as a template
- The implementation is written while considering the type used as the parameter

- Useful when the implementation for different classes differ only by the type used

- Provides a level of abstraction for classes across different variable types

- Data structures are ideal for design as C++ class templates

- Consider, for example, the Stack data structure from previous notes (repeated below)

 

#include <iostream>

using namespace std;

 

#define DEFAULT_SIZE 10

 

class Stack

{

private:

  int *array;   // holds stack values

  int size;     // size of stack

  int topPos;   // top position on stack

 

public:

 

  // Construction

  Stack(int s=DEFAULT_SIZE);

  ~Stack();

 

  // Push on top of stack

  void Push(int);

 

  // Pop off top of stack

  int Pop();

 

  // Is stack empty?

  short IsEmpty() { return (topPos == -1) ? 1 : 0; }

 

  // Overloaded stream operator

  friend ostream &operator<<(ostream &,Stack &);

};

Stack::Stack(int s)

{

  size = s;

  array = size ? new int[size] : NULL;

  topPos = -1;

}

Stack::~Stack()

{

  if( array ) delete[] array;

}

 

// Overloaded output stream operator

ostream &operator<<(ostream &strm, Stack &t)

{

  int i;

  strm << "size: " << t.size;

  strm << "  top position: " << t.topPos << endl;

  strm << "  contents from top:";

  for( i = t.topPos; i >= 0; --i )

    strm << " " << t.array[i];

  strm << endl;

  return strm;

}

 

// Push integer on stack

void Stack::Push(int v)

{

  if( topPos < size-1 )

    {

      // Assign value to top position

      array[ ++topPos ] = v;

    }

  else

    {

      // Cannot push any more values

      cout << "  Warning: stack overflow!" << endl;

    }

}

 

// Pop integer from stack

int Stack::Pop()

{

  if( topPos >= 0 )

    {

      // Return top of stack, decrement current position

      return array[ topPos-- ];

    }

  else

    {

      // Cannot return any more values

      cout << "  Warning: stack underflow!" << endl;

      return 0;

    }

}

 

- This class defines the operation of a stack for a collection of integers

- What if, for example, we require a stack of floats or chars?

- The implementation would be exactly the same and would only differ in the type used

- Rather than duplicating the implementation in other classes, we create a class template
- We denote our stack class to be a template class with a type parameter
T
- Note we can name our parameter anything, doesn't have to be named
T
- When referring to the type in our class, we now use
T

- Using the class above, we add syntax to denote the use of a class template

- We also substitute references to the int variable type used in the stack for the parameter T

 

#include <iostream>

using namespace std;

 

#define DEFAULT_SIZE 10

 

template<class T>

class Stack

{

private:

  T *array;     // holds stack values

  int size;     // size of stack

  int topPos;   // top position on stack

 

public:

 

  // Construction

  Stack(int s=DEFAULT_SIZE);

  ~Stack();

 

  // Push on top of stack

  void Push(T);

 

  // Pop off top of stack

  T Pop();

 

  // Overloaded output stream operator

  // g++ requires in-line definition

  friend ostream &operator<<(ostream &strm, Stack<T> &t)

        {

int i;

strm << "size: " << t.size;

strm << "  top position: " << t.topPos << endl;

strm << "  contents from top:";

for( i = t.topPos; i >= 0; --i )

  strm << " " << t.array[i];

strm << endl;

 

return strm;

  }

 

  // Is stack empty?

  short IsEmpty() { return (topPos == -1) ? 1 : 0; }

};

 

// Constructor

template<class T>

Stack<T>::Stack(int s)

{

  size = s;

  array = size ? new T[size] : NULL;

  topPos = -1;

}

 

// Destructor

template<class T>

Stack<T>::~Stack()

{

  if( array ) delete[] array;

}

 

// Push method

template<class T>

void Stack<T>::Push(T v)

{

  if( topPos < size-1 )

    {

      // Assign value to top position

      array[ ++topPos ] = v;

    }

  else

    {

      // Cannot push any more values

      cout << "  Warning: stack overflow!" << endl;

    }

}

 

// Pop method

template<class T>

T Stack<T>::Pop()

{

  if( topPos >= 0 )

    {

      // Return top of stack, decrement current position

      return array[ topPos-- ];

    }

  else

    {

      // Cannot return any more values

      cout << "  Warning: stack underflow!" << endl;

      return 0;

    }

}

 

 

- Note the special syntax to denote a template definition and the parameter type

 

template < class T >
 - Specifies that a template is being declared
 - Specifies the a parameter type argument named T is to be used

 

- Note the syntax used for references to the class type

 

friend ostream &operator<<(ostream &strm, Stack<T> &t);

 

void Stack<T>::Push(T v)

 

 

- Note instances where the integer type is substituted for the parameter type T

 

array = size ? new T[size] : NULL;  (allocate type T on the heap)

 

T Stack<T>::Pop()                   (return a type T from the function)

 

 

- We can use this class template to manage a stack of any type used in an application

- In our example below, we use the class template to manage a stack of characters and floats

- NOTE: the g++ compiler prefers class template method definitions to be in the same file as the declaration

- For this reason, we place the class declaration and methods in a single header file (Stack.h) included below

 

include "Stack.h"

 

main()

{

  int i;

 

  // stack of characters

  Stack<char> cs(3);

  char cv = 'c';

  cout << endl << "Stack of characters..." << endl;

  for( i = 0; i < 3; ++i, cv+=1 )

    {

      cout << "Pushing value: " << cv << endl;

      cs.Push(cv);

      cout << cs;

    }

  for( i = 0; i < 3; ++i )

    {

      cv = cs.Pop();

      cout << "Popped off value: " << cv << endl;

    }

 

  // stack of floats

  Stack<float> fs(3);

  float fv = 0.0;

  cout << endl << "Stack of floats..." << endl;

  for( i = 0; i < 3; ++i, fv+=1 )

    {

      cout << "Pushing value: " << fv << endl;

      fs.Push(fv);

      cout << fs;

    }

  for( i = 0; i < 3; ++i )

    {

      fv = fs.Pop();

      cout << "Popped off value: " << fv << endl;

    }

}

 

- Note that to use the class template, we use special syntax to inform the compiler the type to substitute

 

Stack<char> cs(3);

 

- At compile time, the parameter T is substituted for the specific type used in a program

- By using class templates, we can write a single class and abstract it for use with multiple types

- We can even use such a class template with our own properly designed classes

- Note that the overloaded output stream operator prints an array of T elements as follows

 

for( i = t.topPos; i >= 0; --i )

    strm << " " << t.array[i];

 

- This assumes that whatever type is substituted for T must also overload its own output stream operator

- If our classes overload the output stream operator, they can work with this class template

- Consider the following Point class to describe a three-dimensional point with location and size

 

// Point class
#include <iostream>

using namespace std;

 

// Point class

class Point

{

 

 private:

  float x, y, z;

  float size;

 

 public:

  // Constructor

  Point( float c1=0.0, float c2=0.0, float c3=0.0 )

    {

      x = c1;

      y = c2;

      z = c3;

      size = 0.0;

    }

 

  // Overloaded += operator

  Point &operator+=(Point &);

 

  // Access methods

  float setSize( float f ) { size = f; }

  float getSize() { return size; }

 

  // Overloaded output stream operator

  friend ostream &operator<<(ostream &strm, Point &);

};

 

// Overloaded output stream operator

ostream &::operator<<(ostream &strm, Point &t)

{

  strm << "  x, y, z: " << t.x << "," << t.y << "," << t.z;

  return strm;

}

 

// Overloaded += operator

Point &Point::operator+=(Point &t)

{

  x += t.x;

  y += t.y;

  z += t.z;

  return *this;

 

- We can use this class with our stack class template as in the following application

- Note we include the class template header file (with declaration and definitions) as above

 

#include <iostream>

using namespace std;

#include "Stack.h"

#include "Point.h"

 

main()

{

  int i;

 

  // stack of Points

  Stack<Point> is(3);

  Point pv(1,2,3);

  Point pi(1,1,1);

  for( i = 0; i < 3; ++i, pv+=pi )

    {

      cout << "Pushing value: " << pv << endl;

      is.Push(pv);

      cout << is;

    }

  for( i = 0; i < 3; ++i )

    {

      pv = is.Pop();

      cout << "Popped off value: " << pv << endl;

    }

 

Output
Pushing value:   x, y, z: 1,2,3
size: 3  top position: 0
  contents from top:   x, y, z: 1,2,3
Pushing value:   x, y, z: 2,3,4
size: 3  top position: 1
  contents from top:   x, y, z: 2,3,4   x, y, z: 1,2,3
Pushing value:   x, y, z: 3,4,5
size: 3  top position: 2
  contents from top:   x, y, z: 3,4,5   x, y, z: 2,3,4   x, y, z: 1,2,3
Popped off value:   x, y, z: 3,4,5
Popped off value:   x, y, z: 2,3,4
Popped off value:   x, y, z: 1,2,3