Templates
Table of Contents
1. Before Templates
Templates don't exist in C++'s precursor, C. Because of this,
if you had a function like - for example - SumTwoNumbers
- that you wanted to work with different data types, you would have to define different functions for each version. C also doesn't have function overloading, so they would have to have different names as well.
As a real-world example, OpenGL is a cross-platform graphics library that can be used to create 3D graphics. OpenGL was written in C, and you could tell it a set of vertices to draw in order to create one polygon or quad or other shape.
There were different functions you could use to define points (vertices) in a shape, like:
glVertex2f( 0, 0 );
glVertex3f( 0, 0, 0 );
And, in particular, there are a bunch of "glVertex" functions: glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, and so on… (Don't you wish you were programming in C?)
2. What are Templates?
With C++ and other languages like C\# and Java, we can now use Templates with our functions and classes. A Template allows us to specify a placeholder for a data type which will be filled in later.
In the C++ Standard Template Library, there are objects like the vector that is essentially a dynamic array, but it can store any data type - we just have to tell it what it's storing when we declare a vector object:
vector<int> listOfQuantities; vector<float> listOfPrices; vector<string> listOfNames;
We can also define our own functions and even classes with templated functions and member variables ourselves, leading to much more reusable code.
2.1. Templated functions
We can write a standalone function with templated parameters or a templated return type or both. For example, here's a simple function to add two items together:
template <typename T> T Sum( T numA, T numB ) { return numA + numB; }
This function can be called with any data type,
so long as the data type has the +
operator
defined for it - so, if it were a custom class you wrote,
you would have to overload the operator+
function.
What this means is that we can call Sum
with integers
and floats, but also with someting like a string, since strings
use the + operator to combine two strings together.
- Calling the templated function:
int main() { int intA = 4, intB = 6; float floatA = 3.9, floatB = 2.5; string strA = "alpha", strB = "bet"; cout << intA << " + " << intB << " = " << Sum( intA, intB ) << endl; cout << floatA << " + " << floatB << " = " << Sum( floatA, floatB ) << endl; cout << strA << " + " << strB << " = " << Sum( strA, strB ) << endl; }
- Program output:
4 + 6 = 10 3.9 + 2.5 = 6.4 alpha + bet = alphabet
2.2. Templated classes
More frequently, you will be using templates to create classes for data structures that can store any kind of data. The C++ Standard Template Library has data structures like vector, list, and map, but we can also write our own.
When creating our templated class, there are a few things to keep in mind:
- We need to use
template $<$typename T$>$
at the beginning of the class declaration. - Method definitions \underline{must be in the header file} - in this case, we won't be putting the method definitions in a separate .cpp file. You can either define the functions inside the class declaration, or immediately after it.
- Method definitions also need to be prefixed with
template <typename T>
.
If you try to create a "TemplatedArray.hpp" file and a "TemplatedArray.cpp" file and put your method definitions in the .cpp file, then you're going to get compile errors:
You might think, "Well, that's weird." - and yes, it is. C++ is a strange language with weird behaviors. In this case in particular, you can read about why this is for templates here: https://isocpp.org/wiki/faq/templates%5C#templates-defn-vs-decl
In short, the template command is used to generate classes, and while our class declaration looks normal, this is actually special code that is just telling the compiler how it's going to generate a family of classes. Because of this, the compiler needs to see the function definitions as well.
3. Example TemplatedArray (Full):
This is all in one file - TemplatedArray.hpp. I have the class declaration on top, with all the definitions below.
#ifndef _TEMPLATED_ARRAY #define _TEMPLATED_ARRAY #include <stdexcept> using namespace std; template <typename T> class TemplatedArray { public: TemplatedArray(); TemplatedArray( int size ); ~TemplatedArray(); void PushToBack( T item ); void RemoveFromBack(); bool IsFull(); bool IsEmpty(); void Display(); int Size(); private: void AllocateMemory( int size ); void DeallocateMemory(); int m_arraySize; int m_storedItems; T* m_array; }; // Constructors/Destructor template <typename T> TemplatedArray<T>::TemplatedArray() { m_arraySize = 0; m_storedItems = 0; // Be safe: Initialize pointers to nullptr. m_array = nullptr; } template <typename T> TemplatedArray<T>::TemplatedArray( int size ) { m_array = nullptr; AllocateMemory( size ); } template <typename T> TemplatedArray<T>::~TemplatedArray() { DeallocateMemory(); } // Other functionality template <typename T> void TemplatedArray<T>::PushToBack( T item ) { if ( IsFull() ) { throw runtime_error( "Array is full!" ); } if ( m_array == nullptr ) { AllocateMemory( 10 ); } m_array[ m_storedItems ] = item; m_storedItems++; } template <typename T> void TemplatedArray<T>::RemoveFromBack() { if ( IsEmpty() ) { throw runtime_error( "Array is empty!" ); } // Lazy deletion m_storedItems--; } template <typename T> bool TemplatedArray<T>::IsFull() { return ( m_arraySize == m_storedItems ); } template <typename T> bool TemplatedArray<T>::IsEmpty() { return ( m_storedItems == 0 ); } template <typename T> void TemplatedArray<T>::Display() { for ( int i = 0; i < m_storedItems; i++ ) { cout << i << ". " << m_array[i] << endl; } } template <typename T> int TemplatedArray<T>::Size() { return m_storedItems; } // Private methods template <typename T> void TemplatedArray<T>::AllocateMemory( int size ) { // Clear out any memory currently stored DeallocateMemory(); m_array = new T[ size ]; m_arraySize = size; m_storedItems = 0; } template <typename T> void TemplatedArray<T>::DeallocateMemory() { // Free the memory allocated if ( m_array != nullptr ) { delete [] m_array; m_array = nullptr; m_arraySize = 0; m_storedItems = 0; } } #endif
This basic templated data structure is now ready to store
any kind of data. In this case, the only requirement is that
the object being stored has the ostream<<
operator
function overloaded, since it is used in the Display() function.
- Using the TemplatedArray
- Within main(), we can then use this templated array to store any kind of data:
#include "TemplatedArray.hpp" #include <iostream> using namespace std; int main() { TemplatedArray<string> myList( 10 ); myList.PushToBack( "cat" ); myList.PushToBack( "rat" ); myList.PushToBack( "bat" ); myList.Display(); myList.RemoveFromBack(); myList.Display(); return 0; }