Class Templates

Class Templates

A class template has much in common with a function template: An "old-style" stack class:
class Stack1
{
  public:
    Stack1(int capacity) : items_(new int[capacity]), count_(0) 
    {
    }
    
    ~Stack1() 
    { 
      delete[] items_; 
    }
    
    void Push(int item) 
    { 
      items_[count_++] = item; 
    }
    
    int Pop() 
    { 
      return items_[--count_]; 
    }
    
    bool IsEmpty() 
    { 
      return (count_ == 0);
    }
    
  private:
    int *items_; // The array of integers
    int count_;  // The number of integers on the stack
};
Using the first Stack class:

int main()
{
  const int SIZE = 5;
  Stack1 s(SIZE);
  
  for (int i = 0; i < SIZE; i++)
    s.Push(i);

  while (!s.IsEmpty())
    std::cout << s.Pop() << std::endl;

  return 0;
}
Output:
4
3
2
1
0
There are some limitations of this Stack class:

  1. No error checking (e.g. Stack may be empty when calling Pop method.)
  2. Size is kind of hard-coded (can't grow the Stack if we need more space, could be wasted unused space.)
  3. Only accepts int type. We could make another class. (What about float, char, StopWatch, etc.?)
// A Stack for ints
class Stack_int
{
  public:
    Stack_int(int capacity) 
    {
     items_ = new int[capacity];
     count_ = 0
    }
    
    ~Stack_int() 
    { 
      delete[] items_; 
    }
    
    void Push(int item) 
    { 
      items_[count_++] = item; 
    }
    
    int Pop() 
    { 
      return items_[--count_]; 
    }
    
    bool IsEmpty() 
    { 
      return (count_ == 0); 
    }
    
  private:
    int *items_;
    int count_;
};
// A Stack for doubles
class Stack_double
{
  public:
    Stack_double(int capacity) 
    {
      items_ = new double[capacity];
      count_ = 0;
    }
    
    ~Stack_double() 
    { 
      delete[] items_; 
    }
    
    void Push(double item) 
    { 
      items_[count_++] = item; 
    }
    
    double Pop() 
    { 
      return items_[--count_]; 
    }
    
    bool IsEmpty() 
    { 
      return (count_ == 0); 
    }
    
  private:
    double *items_;
    int count_;
};

A Stack Implementation using a Template

Class DefinitionImplementations
template <typename T>
class Stack
{
  public:
    Stack(int capacity);
    ~Stack(); 
    void Push(const T& item);
    T Pop(); 
    bool IsEmpty(); 
    
  private:
    T *items_;
    int count_;
};

template <typename T>
Stack<T>::Stack(int capacity)
{
  items_ = new T[capacity];
  count_ = 0;
}
    
template <typename T>
Stack<T>::~Stack() 
{ 
  delete[] items_; 
}

template <typename T>
void Stack<T>::Push(const T& item) 
{ 
  items_[count_++] = item; 
}
    
template <typename T>
T Stack<T>::Pop() 
{ 
  return items_[--count_]; 
}
    
template <typename T>
bool Stack<T>::IsEmpty() 
{ 
  return (count_ == 0); 
}
Quiz: What are the requirements for T in the templated Stack class above?

Pay close attention to the syntax for implementing the templated member functions.

  1. template <typename T>
  2. Stack<T>::
Template syntax:

Implementing the methods within the class:

template <typename T>
class Stack2
{
  public:
    Stack2(int capacity)
    {
      items_ = new T[capacity];
      count_ = 0;
    }
    
    ~Stack2() 
    { 
      delete[] items_; 
    }
    
    void Push(const T& item) 
    { 
      items_[count_++] = item; 
    }
    
    T Pop() 
    { 
      return items_[--count_]; 
    }
    
    bool IsEmpty() 
    { 
      return (count_ == 0); 
    }
    
  private:
    T *items_;
    int count_;
};

Note: When you put the method definitions inside the class (as shown above), you do not need to duplicate the template <typename T> preface that is required outside of the class. I'm only showing this style here to keep the notes from becoming too verbose.


Using this Stack class is almost identical to using the first one:

int main()
{
  const int SIZE = 5;
  Stack2<int> s(SIZE);  // This is the only change
  for (int i = 0; i < SIZE; i++)
    s.Push(i);

  while (!s.IsEmpty())
    std::cout << s.Pop() << std::endl;

  return 0;
}
Output:
4
3
2
1
0


We can now create a Stack of doubles:

int main()
{
  const int SIZE = 5;
  Stack2<double> s(SIZE); // Change type to double
  for (int i = 0; i < SIZE; i++)
    s.Push(i / 10.0); // Push a double

  while (!s.IsEmpty())
    std::cout << s.Pop() << std::endl;

  return 0;
}
Output:
0.4
0.3
0.2
0.1
0


A Stack of char *:

int main()
{
  const int SIZE = 5;
  Stack2<char *> s(SIZE); // Change type to char *

  s.Push("One");
  s.Push("Two");
  s.Push("Three");

  while (!s.IsEmpty())
    std::cout << s.Pop() << std::endl;

  return 0;
}
Output:
Three
Two
One


We can even create a Stack of StopWatch objects:
StopWatch.cpp

int main()
{
  const int SIZE = 5;
  Stack2<StopWatch> s(SIZE); // Change type to StopWatch

  s.Push(StopWatch(60));
  s.Push(StopWatch(90));
  s.Push(StopWatch(120));

  while (!s.IsEmpty())
    std::cout << s.Pop() << std::endl;

  return 0;
}

Output:
00:02:00
00:01:30
00:01:00

The compiler generates code similar to this code for the above. This automatic code generation is called template instantiation.

Notes:

Class templates vs. Function templates:

Non-Type Template Arguments

Suppose we want to create a class that represents an array of integers. We want the size to vary depending on how it's instantiated:
class IntArray1
{
  public:
    IntArray1(unsigned int size) : size_(size), array_(new int[size]) 
    {
    }
    
    ~IntArray1() 
    { 
      delete [] array_; 
    }
    
    // ...
  private:
    int size_;
    int *array_; // dynamically allocated array (at run-time)
};
We can use the class like this:

IntArray1 ar1(20);  // Dynamic allocation at runtime (20 ints)
IntArray1 ar2(30);  // Dynamic allocation at runtime (30 ints)
Using a non-type template parameter:
template <unsigned int size>
class IntArray2
{
  public:
    IntArray2() : size_(size) 
    {
    }
    // ... (no destructor needed)
  private:
    int size_;
    int array[size]; // static array (allocated at compile-time)
};
Using this template class:

IntArray2<20> ar3;  // Static allocation at compile time (20 ints)
IntArray2<30> ar4;  // Static allocation at compile time (30 ints)
Notes:

Multiple and Default Template Arguments

You can specify more than one template argument.

In this modified Array class, we specify not only the type of the elements in the array, but the size as well:

template <typename T, unsigned int size>
class Array
{
  public:
    Array() : size_(size) 
    {
    }
    // ... (no destructor needed)
  private:
    int size_;
    T array[size]; // static array (allocated at compile-time)
};
Usage:
Array<int, 10> ar1;         // int array[10]
Array<double, 20> ar2;      // double array[20]; 
Array<StopWatch, 30> ar3;   // StopWatch array[30];
Array<StopWatch *, 40> ar4; // StopWatch *array[40]
Quiz: What are the requirements for T in the templated Array class above?


BTW, C++11 introduced a new class in the library named std::array, which is similar to the one I'm showing here.

Like function parameters, we can provide defaults for some or all of them:

template <typename T = int, unsigned int size = 10>
class Array
{
  // ... 
};
Usage:
Array<double, 5> ar5; // Array<double, 5>
Array<double> ar6;    // Array<double, 10>
Array<> ar7;          // Array<int, 10>
Array<10> ar8;        // Error, type mismatch (10 is not a type)
Array ar9;            // Error, the Array class is a templated class
What about this? Definition of Stack.
Array<Stack<int>, 20> ar10; // Stack<int> array[20]; ???


One solution:
// Add a default parameter
Stack(int capacity = 10) : items_(new T[capacity]), count_(0) {}

Self-check: Given the classes below, which of the declarations are valid/invalid and why? (They are all constructor calls.)

template <typename T1 = int, int T2 = 10>
class Foo
{
  public:
    Foo(int x = 0) { }
  private:
    T1 items[T2];
};
class A
{
  public:
    A() { }
};
class B
{
  public:
    B(int x) : x_(x) { }
    operator int() 
    {
      return x_;
    }
  private:
    int x_;
};
Remember, you just need to know the requirements of the template parameters. That's how you figure this out.
  1. Foo<int, 5> foo1;
  2. Foo foo2(5);
  3. Foo<int, B(5)> foo3;
  4. Foo<A> foo4(B(5));
  5. Foo<B, 5> foo5;
  6. Foo<B, 5> foo6(5);
  7. Foo<A(), 5> foo7;
  8. Foo<> foo8;
  9. Foo<5> foo9;
  10. Foo<A, 5> foo10;

Class Template Instantiation

Somewhat advanced usage: