Notes on templates in C++

Some notes about templates.

Function templates

Templates can be seen as a kind of automation procedure to generate various version of a function or a class with different types of input arguments. Let’s assume we want to make a basic function which is capable of printing a statement we pass to it. The type of the statement we want to print could for example be a float, int, double, or even a string. One way to handle this kind of different function arguments for the same function would be to simply overload the function and create multiple versions of it. Nevertheless, this is redundant work. Here templates come into play. We simply need to define the function once and then the compiler will generate as many versions of the function as we request through our program. The most simple example is here:

#include <iostream>
#include <string>

template<typename T> // here you can also put class instead of typename
void Print(T value)
{
    std::cout << value << endl;
    return;
}

int main()
{
    Print(5);
    Print<int>(5); // will enforce use of int version
    Print(5.0f);
    Print("5");
    return 0;
}

In this case 4 different templates of the function will be generated whiel compiling the code. If we would not use the function at all, it would not even be compiled.

Another example would be a function which swaps to values:

template<typename T>
void customSwap(T& first, T& second)
{
    T buffer = first;
    first = second;
    second = buffer;
    return;
}

Class templates

The same concept applies to classes. So we could for example declare a template for a vector of a certain size. All the requested sizes will then be declared upon compile time (i.e. we would use 4 element and 3 element versions of the vector):

#include <iostream>

template<int N>
class intVector
{
private:
    int numbers[N];
public:
    int get_size() const {return N;};
};

int main()
{
    intVec<3> threeEx;
    intVec<4> fourEx;

    return 0;
};

In this case two different versions of the class will be created on compile time! So now the next obvious step is that our vector could be of a different datatype. To achieve this, we simply need to add another dimension to our template space:

#include <iostream>

template<typename T, int N>
class intVector
{
private:
    T numbers[N];
public:
    int get_size() const {return N;};
};

int main()
{
    intVec<int, 3> threeEx;

    return 0;
};

Member functions

If we want to initiate functions for those templated classes outside of the class declaration itself (e.g. in an external file), we should follow the same syntax again: for each function which we declare in the class header we should define a function with the template syntax later to allow the compiler to build the different versions of the function.

// definition of class template
template <typename T>
class circle
{
private:
    T radius;
public:
    circle();
    circle(T _radius);
};
// templates scope from first lines runs out here

// definition of member function
template <typename T> // reintroduce another template scope
circle<T>::circle(T _radius) : radius {_radius} {}

Limits of automatic type deduction

While we can always specify the type of the templated class in angle brakcets, most of the time the compiler can deduce the requested type on its own. Nevertheless there are a few exceptions and one is the return type:

template <typename T>
T templFunction(); // some function which returns a certain type

int myNumber = templFunction(); // won't work!
int myNumber = templFunction<int>(); // will work!

Links

Leave a Reply

Your email address will not be published. Required fields are marked *