28. Templates – Modern C++ for Absolute Beginners: A Friendly Introduction to C++ Programming Language and C++11 to C++20 Standards

© Slobodan Dmitrović 2020
S. DmitrovićModern C++ for Absolute Beginnershttps://doi.org/10.1007/978-1-4842-6047-0_28

28. Templates

Slobodan Dmitrović1 
(1)
Belgrade, Serbia
 

Templates are mechanisms to support the so-called generic programming. Generic broadly means we can define a function or a class without worrying about what types it accepts.

We define those functions and classes using some generic type. And when we instantiate them, we use a concrete type. So, we can use templates when we want to define a class or a function that can accept almost any type.

We define a template by typing:
template <typename T>
// the rest of our function or class code
Which is the same as if we used:
template <class T>
// the rest of our function or class code

T here stands for a type name. Which type? Well, any type. Here T means, for all types T.

Let us create a function that can accept any type of argument:
#include <iostream>
template <typename T>
void myfunction(T param)
{
    std::cout << "The value of a parameter is: " << param;
}
int main()
{
}
To instantiate a function template, we call a function by supplying a specific type name, surrounded by angle brackets:
#include <iostream>
template <typename T>
void myfunction(T param)
{
    std::cout << "The value of a parameter is: " << param;
}
int main()
{
    myfunction<int>(123);
    myfunction<double>(123.456);
    myfunction<char>('A');
}

We can think of T as a placeholder for a specific type, the one we supply when we instantiate a template. So, in place of T, we now put our specific type. Neat, ha? This way, we can utilize the same code for different types.

Templates can have more than one parameter. We simply list the template parameters and separate them using a comma. Example of a function template that accepts two template parameters:
#include <iostream>
template <typename T, typename U>
void myfunction(T t, U u)
{
    std::cout << "The first parameter is: " << t << '\n';
    std::cout << "The second parameter is: " << u << '\n';
}
int main()
{
    int x = 123;
    double d = 456.789;
    myfunction<int, double>(x, d);
}
To define a class template, we use:
#include <iostream>
template <typename T>
class MyClass {
private:
    T x;
public:
    MyClass(T xx)
        :x{ xx }
    {
    }
    T getvalue()
    {
        return x;
    }
};
int main()
{
    MyClass<int> o{ 123 };
    std::cout << "The value of x is: " << o.getvalue() << '\n';
    MyClass<double> o2{ 456.789 };
    std::cout << "The value of x is: " << o2.getvalue() << '\n';
}

Here, we defined a simple class template. The class accepts types T. We use those types wherever we find appropriate in our class. In our main function, we instantiate those classes with concrete types int and double. Instead of having to write the same code for two or more different types, we simply use a template.

To define a class template member functions outside the class, we need to make them templates themselves by prepending the member function definition with the appropriate template declaration. In such definitions, a class name must be called with a template argument. Simple example:
#include <iostream>
template <typename T>
class MyClass {
private:
    T x;
public:
    MyClass(T xx);
};
template <typename T>
MyClass<T>::MyClass(T xx)
    : x{xx}
{
    std::cout << "Constructor invoked. The value of x is: " << x << '\n';
}
int main()
{
    MyClass<int> o{ 123 };
    MyClass<double> o2{ 456.789 };
}
Let us make it simpler. If we had a class template with a single void member function, we would write:
template <typename T>
class MyClass {
public:
    void somefunction();
};
template <typename T>
void MyClass<T>::somefunction()
{
    // the rest of the code
}
If we had a class template with a single member function of type T, we would use:
template <typename T>
class MyClass {
public:
    T genericfunction();
};
template <typename T>
T MyClass<T>::genericfunction()
{
    // the rest of the code
}
Now, if we had both of them in a single class and we want to define both of them outside the class scope, we would use:
template <typename T>
class MyClass {
public:
    void somefunction();
    T genericfunction();
};
template <typename T>
void MyClass<T>::somefunction()
{
    // the rest of the code
}
template <typename T>
T MyClass<T>::genericfunction()
{
    // the rest of the code
}

Template specialization

If we want our template to behave differently for a specific type, we provide the so-called template specialization. In case the argument is of a certain type, we sometimes want a different code. To do that, we prepend our function or a class with :
template <>
// the rest of our code
To specialize our template function for type int, we write:
#include <iostream>
template <typename T>
void myfunction(T arg)
{
    std::cout << "The value of an argument is: " << arg << '\n';
}
template <>
// the rest of our code
void myfunction(int arg)
{
    std::cout << "This is a specialization int. The value is: " << arg << '\n';
}
int main()
{
    myfunction<char>('A');
    myfunction<double>(345.678);
    myfunction<int>(123); // invokes specialization
}