The C++ Programming Language

Part 8: Templates

A template in general is a mold or a stencil we use to create copies of a shape. The same applies in C++. Templates are used by the compiler to generate code. This applies both to functions and classes. C++ has the tools we need to design and write our code using generic types. So, our program can work with many data types without rewriting or loading it with specific code just in case we might need it.

What is a template

Templates are one of the most powerful features of C++. They are the blueprints the compiler uses to generate code at compile time as instances of them, as objects are instances of classes at run time. A template takes one or more parameters that you must supply when you instantiate them.

Writing code for templates is called generic programming. This type of programming is independent of data types. We develop our code once and it applies for many data types.

Unlike traditional OOP that is based on polymorphism, template programming is parametric polymorphism. Instead of using specific datatypes the code is developed to handle values without depending on their type. The functions and the data types are called generic functions and generic types, respectively.

Function Templates

Function templates are used to create functions. Here we create a function returning the absolute value of a number. Typically, we would have to write a function for every numerical type: integer, float, double etc. Using templates though we write only one and the compiler generates functions for any type needed. This means that if we do not need it no code will be generated.

// template function
template<typename T>
T absolute_value(T a)
{
    if (a >= T(0))
        return a;
    return -a;
}

Using the template function is easy:

int main() {
    int i = -5;
    int av = absolute_value(i);
    std::cout << av << "\n";
    float f = -5.45;
    float fav = absolute_value(f);
    std::cout << fav << "\n";
}

The compiler sees the call, determines the type of the argument and generates the correct function. Then it generates a call to that function.

The compiler generated two overloaded functions but we have only one piece of code to maintain.

Class Templates

The idea of templates extends to classes as well. They are very useful when they define generic functionality independent of the actual data type. When we were talking about arrays and dynamic memory management we used the vector class. That is actually a template and upon function declaration we were creating the type of vector we needed, like std::vector<int>.

Here is an example of a class template:

template <typename T>
class vec2d {
    T x;
    T y;
public:
    vec2d(T _x=T(0), T _y=T(0)) :x(_x), y(_y) {
    }
};

This class template can be used to hold a two-dimensional vector:

int main() {
    vec2d<int> some_pixel;
    vec2d<float> some_coordinates;
}

Pixel coordinates on the screen are integer values, while coordinates on a two-dimensional diagram are decimals, or in C++ terms float or double. Yet we only have one class to maintain.

Template specialization

Template specialization allows the customization of the template behavior for specific types or conditions.

The absolute_value function we wrote earlier will be our example. First we will simplify by changing the return value to double.

template<typename T>
double absolute_value(T a)
{
    if (a >= T(0))
        return a;
    return -a;
}

This function can handle the built-in numerical types of the language, but what if we have a complex number? The absolute value for a complex number is its magnitude. C++ allows us to define a special version of the template to handle this:

class complex {
public: 
    double a, b;
    complex(double _a, double _b) :a(_a), b(_b) {
    }
};

template<>
double absolute_value<complex>(complex a)
{
    return sqrt(a.a * a.a + a.b * a.b);
}

Note that this extends the template definition and does not add any code in our program unless we call it:

int main() {
    std::cout << absolute_value(2.0001) << "\n";

    complex c(3, 4.5);
    std::cout << absolute_value(c) << "\n";
}

Template specialization can be used in classes as well:

template<typename T>
class templated {
public:
    T value;
    templated(T v) : value(v) {
        std::cout << "generic implementation\n";
    }
};
template<>
class templated<int> {
public:
    int value;
    templated(int v) : value(v){
        std::cout << "specialized implementation\n";
    }
};

The class can differentiate its behavior in the case we have integers:

int main() {
    std::cout << absolute_value(2.0001) << "\n";

    complex c(3, 4.5);
    std::cout << absolute_value(c) << "\n";

    templated d(7.2); // uses the generic
    templated i(7);   // uses the specialized
}

Template Metaprogramming

Template Metaprogramming is a technique of performing computations at compile time using the template mechanism of C++. By default, the C++ optimizes code by performing calculation at compile time whenever this is possible. This stores the precalculated data and saves significant execution time when the program is running. Take a look at the code:

int main() {
    const int i = 3;
    int k = 2 * i;
    int j = 1 << i;
}

All these calculations are based on constant values, so it is safe to perform them at compilation time. So, the compiler does just that, and the code is equivalent to:

const int i = 3;
int k = 6;
int j = 8;

The original code has a big advantage because we will change everything just by changing the value of i and let the compiler do the rest.

Using templates, we can make the compiler perform more complex calculations for our constant values and save us significant execution time.

Here is how we can calculate factorials in compile time. The classic implementation of factorial is like this:

int classic_factorial(int v) {
    if (v <= 1) return 1;
    return v * classic_factorial(v - 1);
}

It is a simple recursive function that we learn as we learn the basics of programming. This code though performs a call and calculations every time:

int main() {
    std::cout << classic_factorial(4) << "\n";
}

It is a constant value though and it would be nice to replace it with a real constant that we could easily update. For this we will create a template struct and let the compiler do it:

// the basic template struct
template<int v>
struct factorial {
    enum { value = v * factorial<v - 1>::value };
};
// specialization required to terminate
template<>
struct factorial<1> {
    enum { value = 1 };
};

So now we can write:

std::cout << factorial<5>::value << "\n";

No matter how many times we use this in our code, or what values we pass to the template, it will always generate constant values, and we will not have to browse through the code to find all references and fix. That is done automatically.

These are the key characteristics of template metaprogramming:

Summary

In this part we have addressed:

The C++ Programming Language