Last Updated:

Templates in C++ with Examples

1. Introduction

Templates provide a simple way to introduce all sorts of common concepts and simple methods for using them together. The resulting classes and functions are comparable in run time and memory requirements to hand-written specialized code.

Templates provide direct support for generalized programming, i.e. programming using types as parameters. The template engine in C++ allows you to use a type as a parameter when defining a class or function. A template depends only on the properties of a type parameter that it explicitly uses, and does not require that the various types used as parameters be related in any other way. In particular, template parameter types should not belong to the same inheritance hierarchy.

A class template defines the data and operations of a potentially unlimited set of related classes. A function pattern defines a potentially unlimited number of related (shared) functions.

The template declaration has the following syntax:
template <list of template parameters> declaration

Here, angle brackets are an element of syntax.

The declaration in the template declaration must describe the function or class. A template declaration can only be global.

The template parameter list cannot be empty. A template with an empty parameter list can simply be described as a regular class or function, and therefore makes no sense.

The process of generating a class declaration from a class template and a template parameter is often referred to as template instantiation. Similarly, a function is generated ("instantiated") from a function template and a template parameter. The version of the template for a particular parameter is called specialization. Generated classes are perfectly ordinary classes that obey all the standard rules for classes (for example, they can have friendly functions). Similarly, generated functions are regular functions that obey all the standard rules for functions.

Templates provide an efficient way to generate code from relatively short definitions. Therefore, some care is required to avoid filling memory with nearly identical function definitions.

2. Feature Templates

A function template specifies how individual functions are built. Function template syntax:
template <the parameter list of the function template> function_type function_name (list of formal parameters) { ... }

All parameters in the list of parameters of a function template must be so-called teep parameters, i.e. parameters that specify the types of formal parameters of the function and/or the value returned by the function.

template <typename TYPE> TYPE abs(TYPE x)
 { return x >= 0 ? x : -x; }

void main()
 { int    i = 567;
   double d = -123.45;

   printf("%7d\n",    abs<int>(i));
   printf("%7.2lf\n", abs<double>(d));
   printf("%7d\n",    abs(i));
   printf("%7.2lf\n", abs(d));
  } 

The type used as a template parameter must provide the interface expected by the template. If we try to substitute a type name into the abs function template for which the >= and operations are not defined, this will lead to an error.

When some function call is identified with a function template, a separate template function is generated with parameters that are precisely identified with the types of actual call parameters.


template <typename TYPE> TYPE max(TYPE a, TYPE b)
 { return a > b ? a : b; }

void main()
 { int    i = 567;
   float  f = 7.5;
   double d = -123.45;

   printf("%7d\n",    max(i, 0));
   printf("%7.2lf\n", max(d, 0));
   printf("%7.2lf\n", max(d, 0.0));
   printf("%7.2lf\n", max(d, f));
   printf("%7.2lf\n", max(d, (double)f));
  } 

A template function can be shared with other (regular) functions and with other template functions.


template <typename TYPE> TYPE max(TYPE a, TYPE b) { return a > b ? a : b; } template <typename TYPE> TYPE max(TYPE a, TYPE b, TYPE c) { TYPE d; d = a; if (b > d) d = b; if (c > d) d = c; return d; }

3. Class Templates

A class template specifies how individual classes are built. Class template syntax:
template <list of class template parameters> class_template_name { ... };

Here, angle brackets are an element of syntax.

The name of the class template must be unique in the program. The list of class template parameters can contain tick parameters as well as regular parameters.

A template-generated class is a perfectly normal class. Therefore, the use of the template does not imply any additional run-time mechanisms beyond those used for an equivalent "manually written" class. On the other hand, the use of templates does not necessarily imply a reduction in the amount of code generated.

After you declare a template, you can declare a class based on that template:
class_name <list of actual template parameters>

This declaration specifies the so-called class name from the template. Each actual template parameter can be a user-defined or standard type name (a tick parameter), a template, or an expression, namely a constant expression or the address of a global object or function or a static class member.


template <class TYPE> class Vector { private: int size; TYPE *v; public: Vector(int n = 0); ~Vector(); ... }; template <class TYPE, int NMAX = 100> class Vector { private: int size; TYPE v[NMAX]; public: Vector(); ~Vector(); ... }; template <class CLASS, void (*err_fun)()> class List { ... };

Each template-generated class receives a copy of each static member of the class template. A static class member definition that is written outside of a class definition must also be a template.


template <class T> class X { private: static T common; static int count; ... }; template <class T> T X<T>::common; template <class T> int X<T>::count = 0;

3.1. Class Member Function Patterns

A member function of a templated class implicitly appears to be a template function whose template parameters are the parameters of the class template.


template <class TYPE, int NMAX> class Vector { private: int size; TYPE v[NMAX]; public: Vector(); Vector(TYPE x); };

This class declaration declares two function patterns. Accordingly, their description should look like this:


template <class TYPE, int NMAX> Vector<TYPE, NMAX>::Vector() { size = NMAX; for (int i = 0; i < size; i++) v[i] = 0; } template <class TYPE, int NMAX> Vector<TYPE, NMAX>::Vector(TYPE x) { size = NMAX; for (int i = 0; i < size; i++) v[i] = x; }

3.2. Friendly Features

A template function does not become an implicitly template function. However, if a function parameter or return value belongs to a templated class, the function becomes a templated function.

template <class T> class X
  {public:
     friend void f1();
     friend X<T>* f2();
     friend int f3(X<T> *p);
     friend void f4(X *p); // Error because class X does not exist
   };
  
void f1() { ... }
template <class T> X<T>* f2 () { ... }
template <class T> int f3(X<T> *p) { ... }



4. Instantiation

The process of generating a class declaration from a class template and a template parameter is often referred to as template instantiation. Similarly, a function is generated ("instantiated") from a function template and a template parameter. The version of the template for a particular parameter is called specialization.

Generated classes are perfectly ordinary classes that obey all the standard rules for classes (for example, they can have friendly functions). Similarly, generated functions are regular functions that obey all the standard rules for functions.

Generating versions of functions from a class template for a set of template arguments is the task of the compiler, not the programmer. Instantiating a class template does not entail instantiating all of its members, or even all the members defined in the class template declaration. Only the member functions of the class that are used are instantiated. This reduces the amount of code generated and avoids the problems that might arise if the template parameter class does not implement any of the operations used by the functions declared in the template.

5. Default template settings

Class templates allow you to set default values for template parameters.


template <class TYPE = int, int NMAX = 10> class Vector { private: int size; TYPE v[NMAX]; ... }; Vector<> v;

Validation of the default argument for a template parameter is performed if and only if the default argument is actually used.

6. Specialization

By default, a template provides a single definition that should be used for all template parameters or combinations of template parameters. This doesn't always make sense to the template developer. For example, you might need different implementations for template parameters that are pointers and parameters that are not pointers. You can solve these problems by providing alternative template definitions and allowing the compiler to make a choice based on the template parameters specified when you used it. These alternate pattern definitions are called custom specializations.

For example, you can define a separate abs function for a template parameter of a char type by specifying an explicit description of the function for that type.
template <> char abs<char>(char x) { return x; }

Let's look at another example. We wrote a vector template, but the pointer parameters require different processing algorithms. In this case, you can also define a separate specialization. First, let's define the specialization of the vector for pointers to void.
template <> class Vector<void*> { ... };

You can now use this specialization as a common implementation for all pointer vectors.

Vector<void*> is a full specialization. That is, there is no template parameter that should be set or that would be displayed during instantiation. This specialization is used with vectors declared as follows:


Vector<void*> vpv;

To determine the specialization that is used for any pointer vector and only for pointer vectors, we will need partial specialization.


template <class T> class Vector<T*> { ... };

The specialization sample <T*> after the name means that this specialization must be used for each type of pointer, that is, this definition is used for each Vector whose template parameter can be expressed as T*.

template <class T> class Vector;
template <class T> class Vector<T*>;
template <> class Vector<void*>; 

A common template must be declared before any specialization.

All template specializations must be declared in the same namespace as the template itself.

One specialization is considered more specialized than another if each parameter list corresponding to the pattern of the first specialization also corresponds to the second specialization, but not vice versa.

template <class T> class Vector;
template <class T> class Vector<T*>;
template <> class Vector<void*>; 

A more specialized version will be preferred in object declarations, pointers, etc., as well as in overload resolution.

7. Use template parameters to select an algorithm

When developing general algorithms, such as sorting or comparing complex objects, such as a string or vector, the criterion for comparing the elements of the object must be specified when performing a specific operation. For example, two different sorting sequences (character numbering methods) are used to compare strings that contain Swedish letters. Therefore, any general solution needs to be expressed in general terms that can be defined not only for a particular type.


template <class T> class String
 { ... };

template <class T, class C> int Compare(const String<T>& s1, const String<T>& s2)
 {
   for (int i = 0; i < s1.Lenght() && i < s2.Lenght(); i++)
    if (!C::eq(s1[i], s2[i]))
     return C::lt(s1[i], s2[i]) ? -1 : 1;
   return s1.Lenght() - s2.Lenght();
  }

template <class T> class UsualCompare
 { public:
    static int eq(T x, T y) { return x == y; }
    static int lt(T x, T y) { return x < y;  }
  };

template <class T> class CompareNoCase
 { public:
    static int eq(T x, T y) { return 0; }
    static int lt(T x, T y) { return 0; }
  };

template <> class CompareNoCase<char>
 { public:
    static int eq(char x, char y)
     { if ('A' <= x && x <= 'Z') x += 'a' - 'A'; if ('A' <= y && y <= 'Z') y += 'a' - 'A'; return x == y; }
    static int lt(char x, char y)
     { if ('A' <= x && x <= 'Z') x += 'a' - 'A'; if ('A' <= y && y <= 'Z') y += 'a' - 'A'; return x < y;  }
  };

String<char> s1, s2;
 
cin >> s1 >> s2;
cout << Compare<char, UsualCompare<char>>(s1, s2)  << endl;
cout << Compare<char, CompareNoCase<char>>(s1, s2) << endl;

Passing comparison operations as a template parameter allows you to pass multiple operations as a single argument at no additional cost at run time. Naturally, comparison operations can be implemented for user-defined types as well as for built-in types. This is an essential point for the implementation of generalized algorithms working with types with non-trivial comparison criteria.

8. Organization of the source code

There are two obvious ways to organize your code using templates:

  1. Include template definitions before they are used in a compilation unit.
  2. include only template declarations before they are used in a compilation unit, and compile their definitions separately.

The first option is bad because everything that the template definition depends on is added to every file that uses that template. The split compilation strategy (the second option) is the logical conclusion of the following thought: if the definition of a pattern is not included in the user's code, none of his dependencies should affect this code.

Note that in order to be accessed from different compilation units, the template definition must be explicitly declared with the export keyword, which means "available from another compilation unit". You can do this by adding export to the template definition or declaration. * Otherwise, the definition must be in scope at the time the template is used.

8. Example

This example develops a template for the Vector class. The parameters of the template are the type of elements of the vector and the number of elements of the vector.

This pattern is then used to generate one-dimensional arrays from real numbers, integer matrices, and vectors from complex numbers.

Vector.h file

 

template <class TYPE, int NMAX> class Vector
 { private:
    int  size;
    TYPE v[NMAX];
   public:
    Vector();
    Vector(TYPE x);
    ~Vector() { }
    int  GetSize() const { return size; }
    TYPE&       operator [] (int n);
    const TYPE& operator [] (int n) const;
    int         operator == (const Vector& vector2);
    Vector      operator +  (const Vector& vector2);
    Vector      operator += (const Vector& vector2);
    Vector      operator -  (const Vector& vector2);
    Vector      operator -= (const Vector& vector2);
    TYPE        operator *  (const Vector& vector2);
  };

template <class TYPE, int NMAX> ostream& operator<< (ostream &f, const Vector<TYPE, NMAX> &v);
template <class TYPE, int NMAX> istream& operator>> (istream &f, Vector<TYPE, NMAX> &v);

Vector file.cpp

 

#include <iostream>
using namespace std;
#include "Vector.h"

template <class TYPE, int NMAX> Vector<TYPE, NMAX>::Vector()
 { size = NMAX;
   for (int i = 0; i < size; i++)
    v[i] = 0;
  }

template <class TYPE, int NMAX> Vector<TYPE, NMAX>::Vector(TYPE x)
 { size = NMAX;
   for (int i = 0; i < size; i++)
    v[i] = x;
  }

template <class TYPE, int NMAX> inline TYPE& Vector<TYPE, NMAX>::operator [] (int n)
 { if (n < 0) n = 0;
   else if (n >= size) n = size - 1;
   return v[n];
  }

template <class TYPE, int NMAX> inline const TYPE& Vector<TYPE, NMAX>::operator [] (int n) const
 { if (n < 0) n = 0;
   else if (n >= size) n = size - 1;
   return v[n];
  }

template <class TYPE, int NMAX> int Vector<TYPE, NMAX>::operator == (const Vector& vector2)
 { for (int i = 0; i < size; i++)
    if (v[i] != vector2.v[i])
     return 0;
   return 1;
  }

template <class TYPE, int NMAX> Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator +  (const Vector& vector2)
 { Vector res;

   for (int i = 0; i < size; i++)
    res.v[i] = v[i] + vector2.v[i];
   return res;
  }

template <class TYPE, int NMAX> Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator += (const Vector& vector2)
 { for (int i = 0; i < size; i++)
    v[i] += vector2.v[i];
   return *this;
  }

template <class TYPE, int NMAX> Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator -  (const Vector& vector2)
 { Vector res;

   for (int i = 0; i < size; i++)
    res.v[i] = v[i] - vector2.v[i];
   return res;
  }

template <class TYPE, int NMAX> Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator -= (const Vector& vector2)
 { for (int i = 0; i < size; i++)
    v[i] -= vector2.v[i];
   return *this;
  }

template <class TYPE, int NMAX> TYPE Vector<TYPE, NMAX>::operator *  (const Vector& vector2)
 { TYPE res = 0;

   for (int i = 0; i < size; i++)
    res += v[i] * vector2.v[i];
   return res;
  }

template <class TYPE, int NMAX> ostream& operator<< (ostream &f, const Vector<TYPE, NMAX> &v)
 { streamsize s = f.width();

   for (int i = 0; i < v.GetSize(); i++)
    f << setw(0) << " " << setw(s) << v[i];
   f << endl;
   return f;
  }

template <class TYPE, int NMAX> istream& operator>> (istream &f, Vector<TYPE, NMAX> &v)
 { for (int i = 0; i < v.GetSize(); i++)
    f >> v[i];
   return f;
  }

File Complex.h**

 

class Complex
 { private:
    double r, m;
   public:
    Complex(double nr = 0, double nm = 0) : r(nr), m(nm) {}
    Complex operator ++();
    Complex operator ++(int);
    Complex operator --();
    Complex operator --(int);
    Complex operator +(const Complex& c) const;
    Complex operator -(const Complex& c) const;
    Complex operator +=(const Complex& c);
    Complex operator -=(const Complex& c);
    bool    operator ==(const Complex& c) const;
    bool    operator !=(const Complex& c) const;
    friend ostream& operator<< (ostream &f, const Complex &c);
    friend istream& operator>> (istream &f, Complex &c);
  };

The main file.cpp

 

#include <iostream>
#include <iomanip>
#include "Vector.cpp"

// See "Source code organization"!
#include "Complex.h"
using namespace std;

void main()
 {
   // Real vector
   Vector<double, 10> v1, v2, v3;
   const Vector<double, 10> v5;

   for (int i = 0; i < v1.GetSize(); i++)
    { v1[i] = i; v2[i] = i + 50; }
   cout << fixed << setprecision(1);
   cout << "v1: " << setw(5) << v1;
   cout << "v2: " << setw(5) << v2;
   v3 = v1 + v2;
   cout << "v1 + v2: " << setw(5) << v3;
   v3 = v1 - v2;
   cout << "v1 - v2: " << setw(5) << v3;
   double r = v1 * v2;
   cout << "r = " << r << endl;
   cout << "v5: " << setw(5) << v5;

   // integer matrix
   Vector<Vector<int, 3>, 3> m1, m2(7), m3;

   cout << "m1" << endl << setw(2) << m1;
   cout << "m2" << endl << setw(2) << m2;
   for (int i = 0; i < m1.GetSize(); i++)
    for (int j = 0; j < m1[i].GetSize(); j++)
     { m1[i][j] = (i + 1) * (j + 1); m2[i][j] = (i + 1) * j + 5; }
   cout << "m1" << endl << setw(2) << m1;
   cout << "m2" << endl << setw(2) << m2;
   m3 = m1 + m2;
   cout << "m3" << endl << setw(2) << m3;
   m3 = m1 - m2;
   cout << "m3" << endl << m3;
   Vector<int, 3> v = m1 * m2;
   cout << "v = " << v << endl;

   // Vector of complex numbers
   Vector<Complex, 3> c1, c2, c3;

   for (int i = 0; i < c1.GetSize(); i++)
    c1[i] = Complex(i, -i);
   for (int i = 0; i < c2.GetSize(); i++)
    c2[i] = Complex(i * 2, -i * 3);
   cout << "c1 = " << setw(4) << c1;
   cout << "c2 = " << setw(4) << c2;
   c3 = c1 + c2;
   cout << "c3 = " << setw(4) << c3;
   c3 = c1 - c2;
   cout << "c3 = " << setw(4) << c3;
   Complex x = c1 * c2; // The operation * is not defined for the Complex class. However, until this operation is used,
                               // the corresponding function for the Vector<Complex, 3> class is not generated from the template and there are no problems
  }