Templates in C++

Template is a method in C++ language to generalize functions/classes to be used with various base types. It allows a same function or class to work with various data types.

  • E.g. STL containers like vector<int>, vector<string>, set<int>, map<int, double>, etc. are templates.

  • E.g. type cast functions like static_cast<int>(3.5)

  • Similar idea to Java generics but with different implementation details

Pro and Con

  • Pro

    • Generalize functions/classes to work with various data types

    • Avoid code redundancy

    • Allow type-safe operations

  • Con

    • Code bloat

      The compiler will generate a copy of the function/class for each base type used. This may cause the executable file to be very large.

    • Hard to debug

      The error messages from the compiler may be hard to understand.

    • Compilation

      The compilation can be very slow if the template is used in many places.

    • Header-only library

      It violates the principle of separation of declaration and implementation.

Alternatives

There are other way to allow generalization of functions to work with different types.

  • function overloading

    • Define many functions like min(int, int), min(double, double), min(char, char), with a same name but different parameter types.

    • Similar logics may be repeated many times.

  • implicit type casting

    • A function min(double, double) can handle calls like min(2, 3), min(2.0, 3), min('a', 66), etc.

    • May cause confusion

    • Not work with complex data types

Sometimes the base types are not used as parameter types but internal variables. In these cases, template is the only solution.

Syntax

  • template keyword

    • template <list of template parameters> clause.

    • The list of template parameters can contain one or more template parameters separated by commas.

    • The template parameters are enclosed in angle-brackets (<>).

    • We only focus on type template parameters, which is used to match a type.

      • Started with keyword class or typename (interchangeable)

    • The template clause must be above the following entities when every the parameterized type is used.

      • function declaration

      • function definition

      • class declaration

      • method definition

     1template<typename T>
     2void func1(T param1);
     3
     4template<typename T>
     5class MyClass{
     6 public:
     7  MyClass(T param1);
     8};
     9
    10// important to notice that the first MyClass is followed by <T> here!
    11// MyClass is the name of the template while MyClass<T> is the
    12//     name of the class
    13template<typename T>
    14MyClass<T>::MyClass(T param1) {
    15  // core logics
    16}
    
  • To use a template

    • Specify the template argument to instantiate the template:

      1// function template
      2int result = func1<int>(10);
      3
      4// class template
      5MyClass<int> obj1;
      6if (obj1.method1(10))
      7  cout << "True" << endl;
      
    • Template argument deduction:

      1func1(10);  // T will be int
      2func1<>(10);  // T will be int
      3func1(10.0);  // T will be double
      4
      5MyClass obj1(10);  // T will be int
      

      Note

      Template argument deduction for class template not available until C++ 17

Split header/implementation file

TL;DR Put everything of a template in a single header file.

Single Header File

A template must be instantiated to provided the compilable source code. The instantiation of templates is lazy. Only when a templated entity is used with base type(s) specified, the template will be instantiated. For instance, vector<int> is an instantiation of the vector template. When an occurrence of vector<int> is seen by the compiler, the compiler will search to see if there is a version of it. If not, the compiler will look for the source code of the vector template and instantiate it to generate the instance of vector<int>.

  • a template itself cannot be compiled

  • a template must be instantiated to generate code to be compiled

  • instantiation is a process to provide really parameter to a template

  • when you instantiate a template in a cpp file and try to compile it, the compiler will look for the template to instantiate from the current cpp file, and all included header files, etc.

  • if the template code (not instantiated) is in another cpp file, the compiler cannot find it and the instantiation will fail.

Thus, it is natural to have a templated function/class in a single header file so that when a cpp file uses the template, the compiler can find all the template code in the header. This method is recommended in our courses.

Split To Multiple Files

Optional Contents Templates requires special organization of the source code because the compiler needs to see both the template definition (not instantiation) and its use within a single translation unit. Thus, it is not possible to split the template into a header file and a cpp file like normal modular code.

There are two work-around methods when the split is preferred:

  1. split implementations in a separate text file and include it in the header file after the declarations. This method is not recommended but commonly seen in old-fashioned courses.

  2. split implementations into a cpp file, explicitly declare all possible instantiations of the template in the cpp file; Thus, when other cpp needs to use the template, the instantiated codes have already been instantiated and ready for use.

Examples

Template Syntax
 1 template<typename T>
 2 T func1(T param1);
 3
 4 template<typename T>
 5 class MyClass{
 6 public:
 7   bool method1(T param1);
 8 }
 9
10 // important to notice that MyClass is followed by <T> here!
11 template<typename T>
12 bool MyClass<T>::method1(T param1) {
13   // logics
14   return true;
15 }
template-demo.hpp
 1#ifndef TEMPLATE_DEMO_HPP
 2#define TEMPLATE_DEMO_HPP
 3
 4// function template
 5template<typename T>
 6T myMin(T, T);
 7
 8template<typename T>
 9T myMin(T val1, T val2) {
10  if (val1 > val2)
11    return val2;
12  else
13    return val1;
14}
15
16// class template
17template<typename T>
18class Min {
19  private:
20  T val1;
21  T val2;
22  public:
23  Min(T val1, T val2);
24  T getMin();
25};
26
27// class methods implementations, not inline
28template<typename T>
29Min<T>::Min(T val1, T val2) {
30  this->val1 = val1;
31  this->val2 = val2;
32}
33
34template<typename T>
35T Min<T>::getMin() {
36  if (val1 > val2)
37    return val2;
38  else
39    return val1;
40}
41
42#endif
template-test.cpp
 1#include "template-demo.hpp"
 2#include <iostream>
 3using namespace std;
 4
 5int main() {
 6  // test myMin function, the function template
 7  cout << "min(2, 3) = " << myMin(2, 3) << endl;  // myMin(int, int)
 8  cout << "min(2.0, 3.0) = " << myMin(2.0, 3.0) << endl;  // myMin(double, double)
 9  // myMin(2, 3.0) cannot compile because types much match
10  // Self-test: now to delare the function to make myMin(2, 3.0) work?
11
12  // test Min class, the class template
13  Min<int> myMin1(2, 3);
14  Min<double> myMin2(2.0, 3.0);
15
16  cout << "The min from the class Min<int>(2, 3) " << myMin1.getMin() << endl;
17  cout << "The min from the class Min<double>(2.0, 3.0) " << fixed
18    << myMin2.getMin() << endl;
19
20}