Templates

When developing “generic” code (code that should work with different data types), templates can be very useful.

Function Templates

Below is a program with a templated print function. Because the function’s templated, it can accept an argument of any data type:

#include <iostream>

template <typename T>
void print(T value) {
    std::cout << value << "\n";
}

int main() {
    print<const char*>("Hello world!");
    print<char>('a');
    print<int>(5);
    print<float>(2.87);
    print<bool>(false);
    return 0;
}

Program Output:

Hello world!
a
5
2.87
0

A function can be templated by using the word “template” followed by a list of template parameters (“typenames”) inside angle brackets. Here, we only create one typename called “T” (this is a very standard typename to use).

Then, any part of the function, whether its the return type, parameter types, or the data types of variables created inside the function, can use the declared typenames from inside the angle brackets. When using the function later on in our code, we substitute each typename with a data type by placing that data type in angle brackets (between the function name and argument list). We call these substitutions the template arguments.

For example, in the program above, we use our typename “T” as a data type for the function’s parameter. Then, when calling the function, we specify exactly what data type T should be. The first time, we substitute T with “const char*“. Then we substitute T with “char“, and so on.

Note that, because the “template <typename T>” line does not end in a semi colon or an open brace, that template only applies to the next function. Any functions following cannot use that function template’s template parameters (e.g. “T“).

Here’s what this program would look like with multiple template parameters:

#include <iostream>

template <typename T1, typename T2>
void print(T1 value1, T2 value2) {
    std::cout << value1 << "   " << value2 << "\n";
}

int main() {
    print<const char*, int>("Hello world!", 5);
    print<char, float>('a', 3.14);
    return 0;
}

Program Output:

Hello world!   5
a   3.14

In the first line of the main function, we substitute T1 with “const char*” and T2 with “int“. Therefore, the first parameter of the function must be const char*, and the second parameter must be int.

Why Templates Work

Templates work because the compiler can automatically make a copy of a function for every combination of template arguments passed to it. For instance, in the last example, the compiler would automatically generate two functions that look like this:

void print(const char *value1, int value2) {
    std::cout << value1 << "   " << value2 << "\n";
}

void print(char value1, float value2) {
    std::cout << value1 << "   " << value2 << "\n";
}

Basically, templates are just automated copy-pasting.

typename

The “typename” keyword in a template parameter list can be replaced with the “class” keyword. Both are essentially the same:

#include <iostream>

template <class T>
void print(T value) {
    std::cout << value << "\n";
}

int main() {
    print<char>('a');
    print<int>(5);
    return 0;
}

Another Example

Here’s another function template, this time using the template parameters in more places:

#include <iostream>
#include <cmath> //For abs()

template <typename T>
T power(T base, int exponent) {
    bool negativeExponent = exponent < 0;
    exponent = abs(exponent); //Take absolute value of exponent

    T result = 1;
    for (int i = 0; i < exponent; i++) {
        result *= base;
    }
    
    if (negativeExponent) {
        return 1 / result;
    }
    return result;
}

int main() {
    std::cout << "5.2367495634^2 = " << power<double>(5.2367495634, 2) << "\n";
    std::cout << "2^0 = " << power<int>(2, 0) << "\n";
    std::cout << "3^-2 = " << power<int>(3, -2) << "\n";
    std::cout << "3.0^-2 = " << power<float>(3.0, -2) << "\n";
    return 0;
}

Program Output:

5.2367495634^2 = 27.4235
2^0 = 1
3^-2 = 0
3.0^-2 = 0.111111

If you’re curious, there is a way to only allow arithmetic data types (ints, floats, doubles, etc.) to work with a template function, but it’s a bit messy:

#include <iostream>
#include <cmath> //For abs()

template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
T power(T base, int exponent) {
    bool negativeExponent = exponent < 0;
    exponent = abs(exponent); //Take absolute value of exponent

    T result = 1;
    for (int i = 0; i < exponent; i++) {
        result *= base;
    }
    
    if (negativeExponent) {
        return 1 / result;
    }
    return result;
}

int main() {
    std::cout << "5.2367495634^2 = " << power<double>(5.2367495634, 2) << "\n";
    std::cout << "2^0 = " << power<int>(2, 0) << "\n";
    std::cout << "3^-2 = " << power<int>(3, -2) << "\n";
    std::cout << "3.0^-2 = " << power<float>(3.0, -2) << "\n";
    power<const char*>("Hi", 3); //Doesn't work: "Hi" is not arithmetic
    return 0;
}

Template Specializations

To give a template specialized behavior for a certain set of types, we can “specialize” the template. Here, I create a template specialization for the print function that’s used only if the provided template argument is an int:

#include <iostream>

template <class T>
void print(T value) {
    std::cout << value << "\n";
}

template <>
void print<int>(int value) {
    std::cout << "Int function specialization called! Value: " << value << "\n";
}

int main() {
    print<char>('a');
    print<float>(5.89);
    print<int>(5);
    return 0;
}

Program Output:

a
5.89
Int function specialization called! Value: 5

Template Argument Deduction

The template arguments for a function can usually be deduced automatically, meaning that writing them is often unnecessary:

#include <iostream>

template <typename T>
void print(T value) {
    std::cout << value << "\n";
}

int main() {
    print("Hello world!");
    print('a');
    print(5);
    print(2.87);
    print(false);
    return 0;
}

Class Templates

Classes can be templated just like functions, allowing any of their inner variables/functions to use their template parameters:

#include <iostream>

template <typename T>
class Point {
    private:
        T x;
        T y;
    
    public:
        Point(T x, T y) {
            this->x = x;
            this->y = y;
        }
        void display() {
            std::cout << "(" << x << ", " << y << ")\n";
        }
};

int main() {
    Point<int> point1(3, 7);
    point1.display();

    Point<float> point2(2.4345, 1.8599);
    point2.display();

    Point<char> point3('a', 'z');
    point3.display();

    return 0;
}

For class templates, we specify the data types to use in angle brackets following the class name. For example, T is an int for point1 above, causing point1‘s variables (x and y) and constructor parameters to be of type int.

Just like function templates, the “template <...>” line at the top of a class template only applies to the next class. Any following functions or classes cannot use that class template’s template parameters.

Separating Class Templates into Files

Because of how templates work, class templates can’t be separated into a header file and “.cpp” file like standard classes. This is because the compiler can only make copies of a class or function template when the usage of that class or function template (e.g. Point<int> point1(3, 7); ) is in the same compile unit (“.cpp” file) as its implementation (e.g. template <typename T> class Point { ... }; ) . This, in turn, is because the C++ compiler must be able to compile each “.cpp” file independently, so, with separated usage and implementation of a template, the compiler wouldn’t be able to determine what copies of the templated function/class to make.

A Workaround

One workaround is to do something like this:

Point.h:

#ifndef POINT_H
#define POINT_H

#include <iostream>

template <typename T>
class Point {
    private:
        T x;
        T y;
    
    public:
        Point(T x, T y);
        void display();
};

#include "Point.inl" // <-- Notice this

#endif

Point.inl (some may also use “.tpp”):

template <typename T>
Point<T>::Point(T x, T y) {
    this->x = x;
    this->y = y;
}

template <typename T>
void Point<T>::display() {
    std::cout << "(" << x << ", " << y << ")\n";
}

main.cpp:

#include "Point.h"

int main() {
    Point<int> point1(3, 7);
    point1.display();

    Point<float> point2(2.4345, 1.8599);
    point2.display();

    Point<char> point3('a', 'z');
    point3.display();

    return 0;
}

When the C++ preprocessor runs through Point.h, that file will be modified to look something like this (note how Point.inl is added to the bottom):

#ifndef POINT_H
#define POINT_H

#include <iostream>

template <typename T>
class Point {
    private:
        T x;
        T y;
    
    public:
        Point(T x, T y);
        void display();
};

template <typename T>
Point<T>::Point(T x, T y) {
    this->x = x;
    this->y = y;
}

template <typename T>
void Point<T>::display() {
    std::cout << "(" << x << ", " << y << ")\n";
}

#endif

Therefore, when the preprocessor runs through main.cpp, that file will be modified to look like this:

#define POINT_H

#include <iostream>

template <typename T>
class Point {
    private:
        T x;
        T y;
    
    public:
        Point(T x, T y);
        void display();
};

template <typename T>
Point<T>::Point(T x, T y) {
    this->x = x;
    this->y = y;
}

template <typename T>
void Point<T>::display() {
    std::cout << "(" << x << ", " << y << ")\n";
}

int main() {
    Point<int> point1(3, 7);
    point1.display();

    Point<float> point2(2.4345, 1.8599);
    point2.display();

    Point<char> point3('a', 'z');
    point3.display();

    return 0;
}

Now the implementation of the class’s functions is in the same “.cpp” file as the usage of those functions, so the compiler can do its job properly.

One thing that may look a bit strange is how we need to add a “template <typename T>” to the top of each function implementation. This is just so that the compiler knows what “T” is. Additionally, because Point is a template class, we need to add the “<T>” in “Point<T>::Point” and “Point<T>::display“. As an example, if T were an int, the constructor implementation would look like this:

Point<int>::Point(int x, int y) {
    this->x = x;
    this->y = y;
}

Non-Type Template Parameters

Strangely enough, template parameters don’t even need to be typenames:

#include <iostream>

template <int NUMBER>
void printNumber() {
    std::cout << NUMBER << "\n";
}

int main() {
    printNumber<125>();
    printNumber<10>();
    return 0;
}

Program Output:

125
10

Unlike regular function parameters, however, these are always constants, and their value is always known at compile time. This is because, like mentioned earlier, the compiler creates a copy of the printNumber function (in this case) for every set of template arguments used. Therefore, the compiler automatically creates these functions for us:

void printNumber() {
    std::cout << 125 << "\n";
}

void printNumber() {
    std::cout << 10 << "\n";
}

This can be compared with work done at run time: anything that happens after starting our program with “.\main“. With work done at compile time, tasks (like generating those above two functions) are performed even before the program actually starts.

Template Metaprogramming

Now, it turns out that, in C++, literally anything can be done at compile time using a technique called template metaprogramming. This is essentially just the use/abuse of non-type template parameters to perform tasks before ever starting a program.

Why do this? By performing some tasks at compile time, a program at run time can work a lot faster (since it would have fewer tasks to perform). This is nice because we often compile a computer program once and run the program multiple times afterwards (e.g. compiling a program for a robot and then running the robot’s program at different competitions). It’s not usually recommended to use template metaprogramming often, though, since it can start to look really messy, but it can be a bit fun to learn about.

Here’s a program that uses struct templates (those work just like class templates) to calculate the factorial of a number using recursion at compile time (see Functions for a refresher on recursion):

#include <iostream>

template <int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template <>
struct Factorial<1> {
    static const int value = 1;
};

int main() {
    std::cout << Factorial<4>::value << "\n";
    return 0;
}

When the compiler sees “Factorial<4>” in the main function, one can imagine it creating a Factorial template specialization that looks like this, based on the template at the top of the program:

template <>
struct Factorial<4> {
    static const int value = 4 * Factorial<3>::value;
};

Now it sees “Factorial<3>“, so it creates another structure for that:

template <>
struct Factorial<3> {
    static const int value = 3 * Factorial<2>::value;
};

Then one for “Factorial<2>“:

template <>
struct Factorial<2> {
    static const int value = 2 * Factorial<1>::value;
};

And there’s already a template specialization for Factorial<1>, so the compiler is finished with creating new structures. At this point, the code can be imagined looking something like this:

#include <iostream>

template <int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template<>
struct Factorial<4> {
    static const int value = 4 * Factorial<3>::value;
};

template<>
struct Factorial<3> {
    static const int value = 3 * Factorial<2>::value;
};

template<>
struct Factorial<2> {
    static const int value = 2 * Factorial<1>::value;
};

template <>
struct Factorial<1> {
    static const int value = 1;
};

int main() {
    std::cout << Factorial<4>::value << "\n";
    return 0;
}

Now the compiler can work backwards and calculate the value of Factorial<4>.

  • 2 * Factorial<1>::value = 2 * 1 = 2
  • 3 * Factorial<2>::value = 3 * 2 = 6
  • 4 * Factorial<3>::value = 4 * 6 = 24, which is our final answer

All of this without even typing “.\main“! This means that all running our program does is print the number 24.

constexpr

Recently (since C++11), the constexpr keyword has made it easier to do things at compile time. Just by placing the word right before a function name (most of the time) can allow that function to be run as the program is compiled. Here’s how factorial can be calculated with recursion and constexpr:

constexpr int factorial(int number) {
    if (number == 1) {
        return 1;
    }
    return number * factorial(number - 1);
}

Challenge Problem

As we saw earlier, the data type of a lambda is unspecified, so we have to use the “auto” type when working with them. We also saw some nice algorithms that take a lambda as a parameter, but parameters can’t be of type auto. So how can we work around this? Templates!

Create a function template called “doFiveTimes” that has one parameter for a lambda (with data type “T“). As you can probably guess, the function should call that lambda five times; the lambda can do whatever you want.

If you get stuck, check out my solution here.