What’s The Problem?

What’s the Problem?

Just as for function templates, which address the issue of dealing with functions where the algorithm is the same, but the need for supporting different types, class templates address the same issue, but for structures/classes.

Consider a Stack class, which implements a stack of ints:

//
//  Stack.h
//  Class Templates
//
//  Created by Bryan Higgs on 10/16/24.
//

#ifndef Stack_h
#define Stack_h

class Stack
{
public:
  Stack(size_t s)
    : m_base(new int[s]), m_size(s)
  {
    m_top = m_base;
  }
  
  ~Stack()
  {
    delete [] m_base;
  }
  
  void Push(int a)
  {
    *m_top++ = a;
  }
  
  int Pop()
  {
    return *--m_top;
  }
  
  size_t Size() const
  {
    return m_top - m_base;
  }

private:
  int*    m_base; // The stack
  int*    m_top;  // The current top
  size_t  m_size;  // The size of the stack
};

#endif /* Stack_h */
//
//  main.cpp
//  Class Templates
//
//  Created by Bryan Higgs on 10/16/24.
//

#include <iostream>

#include "Stack.h"

int main(int argc, const char * argv[])
{
  Stack mystack(100);

  for (int i = 0; i < 10; i++)
  {
    mystack.Push(i);
  }
  
  std::cout << "Size = " << mystack.Size() << std::endl
            << "Values: ";
  
  for (int i = 0; i < 10; i++)
  {
    std::cout << mystack.Pop() << ' ';
  }
  std::cout << std::endl;

  return 0;
}

Which outputs:

Size = 10
Values: 9 8 7 6 5 4 3 2 1 0 
Program ended with exit code: 0

That’s all well and good for ints, but what about stacks of other types?

We could write new classes:

  • StackOfInts
  • StackOfLongs
  • StackOfChars
  • StackOfMumblefratzes

etc., but this rapidly becomes repetitive, tedious, and error-prone. (Not to mention polluting the namespace!)

And it doesn’t help with user-defined types, where a vendor doesn’t know what types will exist for customers.

The implementation of something like a Stack is largely independent of what type the stack supports.  The algorithms and structure remain identical; only the types change. 

So what to do?

One Solution: Use typedefs

Note that the following is identical to the original, except that every int that relates to the type of stack has been changed to TYPE.

//
//  GenStack.h
//  GenStack
//
//  Created by Bryan Higgs on 10/16/24.
//

#ifndef GenStack_h
#define GenStack_h

//  A class Stack of TYPEs
// (no error checking, for simplicity)
//
class Stack
{
public:
  Stack(size_t s)
    : m_base(new TYPE[s]), m_size(s)
  {
    m_top = m_base;
  }
  
  ~Stack()
  {
    delete [] m_base;
  }
  
  void Push(const TYPE a)
  {
    *m_top++ = a;
  }
  
  TYPE Pop()
  {
    return *--m_top;
  }
  
  size_t Size() const
  {
    return m_top - m_base;
  }

private:
  TYPE*   m_base; // The stack
  TYPE*   m_top;  // The current top
  size_t  m_size; // The size of the stack
};

#endif /* GenStack_h */
//
//  main.cpp
//  GenStack
//
//  Created by Bryan Higgs on 10/16/24.
//

#include <iostream>

// Choose type for Stack
// (before #include)
typedef const char* TYPE;

#include "GenStack.h"

int main(int argc, const char * argv[])
{
  const char* values[] = {"Mabel","George","Joe"};

  Stack mystack(100);
  
  for (int i = 0; i < 3; i++)
  {
    mystack.Push(values[i]);
  }
  std::cout << "Size = "
  << mystack.Size() << std::endl
       << "Values: ";
  
  for (int i = 0; i < 3; i++)
  {
    std::cout << mystack.Pop() << ' ';
  }
  std::cout << std::endl;

  return 0;
}

Which outputs:

Size = 3
Values: Joe George Mabel 
Program ended with exit code: 0

Note: We have to specify what TYPE actually is before we include the definition of Stack.  

We could have used a macro instead of a typedef, but a typedef is safer.

This solution is less than perfect:

  • You can’t easily use this mechanism to handle more than one kind of stack in the same compilation unit.
  • Although you could extend the mechanism to allow the use of different kinds of stacks in the same compilation unit, using macros, it would involve changing the class name, which starts to pollute the namespace again.
  • The interface to the class(es) is more complex, and it is up to the user to remember all the requirements;  if any are not met, strange things can happen.
  • Generally, this solution is a ‘hack’.

The Real Solution: Class Templates

The C++ solution is class templates.

Here is a template Stack class:

//
//  TemplStack.h
//  Template Stack
//
//  Created by Bryan Higgs on 10/16/24.
//

#ifndef TemplStack_h
#define TemplStack_h

template <typename TYPE>
class Stack
{
public:
  Stack(size_t s)
    : m_base(new TYPE[s]), m_size(s)
  {
    m_top = m_base;
  }
  
  ~Stack()
  {
    delete [] m_base;
  }
  
  void Push(const TYPE a)
  {
    *m_top++ = a;
  }
  
  TYPE Pop()
  {
    return *--m_top;
  }
  
  size_t Size() const
  {
    return m_top - m_base;
  }

private:
  TYPE*   m_base; // The stack
  TYPE*   m_top;  // The current top
  size_t  m_size; // The size of the stack
};

#endif /* TemplStack_h */
//
//  main.cpp
//  Template Stack
//
//  Created by Bryan Higgs on 10/16/24.
//

#include <iostream>
#include "TemplStack.h"

int main(int argc, const char * argv[]) 
{
  const char* values[] = {"Mabel","George","Joe"};

  Stack<const char *> mystack(100);
  
  for (int i = 0; i < 3; i++)
  {
    mystack.Push(values[i]);
  }
  std::cout << "Size = " 
            << mystack.Size() << std::endl
            << "Values: ";
  
  for (int i = 0; i < 3; i++)
  {
    std::cout << mystack.Pop() << ' ';
  }
  std::cout << std::endl;

  return 0;
}

Which outputs:

Size = 3
Values: Joe George Mabel 
Program ended with exit code: 0

Note: Note that this is just like the typedef approach, except with

template <typename TYPE>

preceding it

… and when a variable is declared as a template type, the actual TYPE must be specified:

Stack<int> myintstack; // TYPE is int

A template can be thought of as:

‘a clever kind of macro that obeys the scope, naming, and type rules of C++’

~ Stroustrup
Index