Introduction to Templates

Template functions

Templates in C++ is a feature that allows for generic programming. Using templates a programmer can write code which is not dependent on the data type. Consider the following function that adds two numbers:

int add(int first, int second)
{
    return a+b;
}

The above function will add two integers. If you want to implement a function for adding floats, then a new function needs to be written like this:

float add(float first, float second)
{
    return a+b;
}

The only difference between the two functions is of the data type. The basic logic for both the functions is same. Templates allow us to write functions and classes in such a way that it is independent of the data type.

Consider the following function which has been re-written as a template:

template <typename T>
T add(T first, T second)
{
    return a+b;
}

The first line template <typename T> specifies that the following function is written as a template, and in the function wherever the keyword T appears it is to treated as a generic data-type, whose actual type will be resolved later on. In the header or body of the function we have specified the data-type of parameters and the return value as T.

The template function is invoked like a normal function, and the actual type of T will be resolved as per how the add function is invoked. e.g. if we call the add function as add(1,2), then T will get resolved to int and thus the data-type of parameters and return value will become as int. Now, if add function is again invoked as add(1.9, 2.9), then the type of T will resolve to float. So, templates are not restructed to a single type only.

Following is a complete program using the temaplate example:

#include <iostream>
using namespace std;
template <typename T>
T add(T first, T second)
{
    return first+second;
}
int main()
{
    cout<<add(1,2)<<endl;
    cout<<add(1.9,2.9)<<endl;
    return 0;
}

Behind the scenes

One question that comes to mind is that when is the actual type of T resolved - at run-time or at compile-time ? The way compilers implement templates is that when they encounter a template function, instead of generating the code for the function it waits until the template function is actually called. Upon looking at the instance of function call, the data-type of T is determined and complier generates a copy of template function replacing T with the data-type used while calling the function. This is called as instantiating the template. When the compiler encounters another call to template function with type of arguments different than prevously encountered, then it will generate another copy of template function (or another instance of template) replacing T with the new type encountered i.e.

In the above example, when compiler encounters the function call add(1,2), it generates a copy of add function with T defined as int. The following function would be added to the code of program:

int add(int first, int second)
{
    return first+second;
}

Now when the compiler encounters the function call add(1.9, 2.9), it generates a copy of add function with T defined as float. The following function would be added to the code of program:

float add(float first, float second)
{
    return first+second;
}

All of this is done during compile time, which means that the program actually has two copies of function add defined with it. We can verify this by inpsecting the symbol table of the generated executable.

#include <iostream>
using namespace std;
template <typename T>
T add(T first, T second)
{
    return first+second;
}
int main()
{
    cout<<add(1,2)<<endl;
    cout<<add(10,20)<<endl;
    return 0;
}
$ g++ -g -o template.cc -o template
$ nm -A template | grep add
template: 0000000100000d55 T __Z3addIiET_S0_S0_

We can see that only one instance of add function was generated.

However, if we modify the main function to call the add fucntion with float arguments

int main()
{
    cout<<add(1,2)<<endl;
    cout<<add(1.9,2.9)<<endl;
    return 0;
}
$ g++ -g -o template.cc -o template
$ nm -A template | grep add
template: 0000000100000cfa T __Z3addIdET_S0_S0_
template: 0000000100000ce8 T __Z3addIiET_S0_S0_

Here we can see that two different instances of add function have been generated in the executable, one for int and another for float.

Template Overloading

Templates can be overloaded for different parameter types as well i.e. two template functions with same name but different parameters

#include <iostream>
template <typename T>
T add(T first, T second)
{
    return first+second;
}
template <typename T>
T add(T first, T second, T third)
{
    return first+second+third;
}
int main()
{
    std::cout<<add(1,2)<<std::endl;
    std::cout<<add(1,2,3)<<std::endl;
    return 0;
}

Here we can observe, that the compiler generated two functions from the template. First for the function with two parameters, and second for the three parameters.

$ nm -A template | grep add
template: 0000000100000d0a T __Z3addIiET_S0_S0_
template: 0000000100000d1c T __Z3addIiET_S0_S0_S0_

Specialized template

It is possible to have a template function specifically tailored for a specific data-type e.g. we can have a template function for all the in-built data types, while we can have a specialized function that adds a structure or a class instance.

If we need to specify a template function specifically for a class, e.g. a Rectangle class then we need to define it as

template <>
Rectangle add<Rectangle>(Rectangle first, Rectangle second)

This definition of specialized template function differs from a normal template function definition in following two ways:

    1. Specialized template function starts with template <>
    2. The name of the function is add <Rectangle>

Following is the complete example

#include <iostream>
using namespace std;
class Rectangle
{
    public:
        int length;
        int breadth;
};
template <typename T>
T add(T first, T second)
{
    return first+second;
}
template <>
Rectangle add<Rectangle>(Rectangle first, Rectangle second)
{
    Rectangle v;
    v.breadth = first.breadth + second.breadth;
    v.length = first.length + second.length;
    return v;
}
int main()
{
    Rectangle v1, v2, v3;
    v1.length = 1;
    v1.breadth = 3;
    v2.length = 1;
    v2.breadth = 6;
    cout<<add(1,2)<<endl;
    v3 = add(v1,v2);
    cout<<v3.breadth<<endl;
    return 0;
}

Explicit instantiation

What we have seen so far is that the compiler generates the instance of a template when a function all is encountered. It is also possible to force the compiler to generate an instance of the template function even if a function call is not present in the code.

If a template function has been defined like following:

template <typename T> T function-name(T param-name) { ... }

Then an explicit instance of the function can be created by adding the following line in the code:

template return-type function-name(parameter-type param-name);

Following code shows an example of explicit instantiation of the function add

#include <iostream>
using namespace std;
template <typename T>
T add(T first, T second)
{
    return first+second;
}
// Explicit instantiation
template float add(float first, float second);
int main()
{
    cout<<add(1,2)<<endl;
    return 0;
}

The number of instances can be verified by looking at the symbol table of the generated executable:

$ nm -A template | grep add
template: 0000000100000c9f T __Z3addIfET_S0_S0_
template: 0000000100000d27 T __Z3addIiET_S0_S0_

When would explicit instantiation be used

Explicit instantiation might be most useful if template is present in libraries (.a, .so, or .lib) files, where code is compiled first and later on the applications using them would be written. So while compiling the library containing the template, the compiler would not know for whch types the template has to be instantiated. In such a scenario, the developer might force the compiler to generate the instances for types that he/she knows function would be used for by the applications.

One might ask, then why not just create the functions for different types instead of using template in libraries. The advantage of using templates is that it allows code reuse. Having a single template function rather than different functions for each type, saves time and is easier to maintain as well. If there is any change or bug-fix required then only one function needs to be changed instead of all the differert functions. Because of this reason it is good idea to use templates with explicit instantaition in normal programs as well (wherever possible.)

Difference between explicit instantiation and specialization

There are couple of differences between explicit instantiation and specialization:

    1. Specialization allows to have an implementation different than the one specified in the template function, while in explicit instantiation the function implementation is still the same.
    2. Specialization requires that complete function body be defined. Explicit Instantiation requires only definition of the function with the types defined, but not the body of the function.
    3. For a template defined as following :
      1. template <typename T> T function-name(T param-name)
      2. {
      3. ...
      4. }
    4. Syntax for specialization is:
      1. template <> return-type function-name(parameter-type param-name)
      2. {
      3. ...
      4. }
    5. And, syntax for explicit instantiation is: (notice the missing angular brackets (<>) after the keyword template)
      1. template return-type function-name(parameter-type param-name);

Resolving function from different options

C++ allows that in a program a normal function and template function can be present with the same name.

Consider the following example:

#include <iostream>
using namespace std;
int add(int first, int second)
{
    return first+second;
}
template <typename T>
T add(T first, T second)
{
    return first+second;
}
template <> 
int add<int>(int first, int second)
{
    return first+second;
}
int main()
{
    cout<<add(1,2)<<endl;
    cout<<add(2,3)<<endl;
    return 0;
}

In the above code, the function call add(1,2) could be matched to either of the following:

    1. The non-template add function: int add(int first, int second)
    2. The generic template add function: template T add(T first, T second)
    3. The specialized template add function: template <> int add(int first, int second)

All of the three functions are allowed by the compiler at the same time in the code. However, when this happens, the complier needs to choose the function to call among the alternatives. The compiler follows the below mentioned rules while resolving the fuction call if multiple options are available:

    1. Match the non-template function first
    2. Match the specialized template function, if non-template function is not available
    3. Match the template function, if specialized template function is not available

Non-type arguments

A function that has been defined as a template function can have parameters which have their types defined along with the template type.

#include <iostream>
using namespace std;
template <typename T>
T power(T base, int exponent)
{
    T retValue = 1;
    for(int i=1; i <=exponent; i++)
    {
        retValue *= base;
    }
    return retValue;
}
int main()
{
    cout<<power(5,3)<<endl;
    return 0;
}

Multiple type templates

Function written as templates are not limited to one generic type parameters. There can be multiple type parameters passed to the template functions.

template <typename T1, typename T2>
int get_index(T1 iterator, T2 data)
{
    int index  =-1;
    int tempIndex = 0;
    while(!iterator.end())
    {
        if ((*iterator) == data)
        {
            index = tempIndex;
            break;
        }
        tempIndex++;
        ++iterator;
    }
    return tempIndex;
}

Template classes

Classes can also be defined as templates. Type parameters can be passed to class which can be used to define its members and functions. A class template is defined as following:

template <typename T>
class <class-name>
{  
    ... class definition ...
};

Following is a sample template class definition:

template <typename T>
class myClass
{  
    T myData;
};

However, syntax for creating a instance of template class is different than usual. A instance of template class is defined as

classname<type> class-instance-name;

or using the new operator in following way:

classname<type> *class-instance-name = new className<type>();

Therefore, the instance of myClass shown above could be defined as:

myClass<int> c;

or

myClass<int> *c = new myClass<int>();

Following is a complete example of a template class:

#include <iostream>
using namespace std;
template <typename T>
class Message
{
    public:
        T contents;
        Message(T contents)
        {
            this->contents = contents;
        }
};
int main()
{
    Message<int> msg(10);
    cout<<msg.contents<<endl;
    return 0;
}

The instance of Message class could also be declared using the new operator in the following way:

Message<int> *msg = new Message<int>(30);

Template classes have all the features defined for template functions i.e. specialization, explicit instantiation, non-type arguments, multiple-type arguments, etc.

Inheriting Template classes

Template classes can be inherited as well. Class that inherits a template class needs to be defined as template class as well. The following example shows how a class inherits a template class.

#include <iostream>
using namespace std;
template <typename T>
class Message
{
    public:
        T contents;
        Message(T contents)
        {
            this->contents = contents;
        }
};
template <typename T>
class Packet: public Message<T>
{
    public:
        int message_id;
        Packet(int message_id, T contents):Message<T>(contents)
        {
            this->message_id = message_id;
        }
};
int main()
{
    Packet<int> packet(1, 10);
    cout<<packet.contents<<endl;
    return 0;
}

In the above example following things are to be noticed:

    1. Even though the derived class does not have members or functions of its own that using template type, even then it is defined as :
      1. template <typename T>
      2. class Packet: public Message<T>
      3. { ... };
    2. The name of the base class while defining the derived class is mentioned as : Message<T>
    3. The constructor of the base class in derived class constructor is invoked as: Message<T>(contents)

Passing instance of template class as an argument

Functions that need to receive instance of template class in their arguments should be defined as template functions as well:

template <typename T>
void debugPrint(Packet<T> pkt)
{
    cout<<"Msg Id: "<<pkt.message_id<<"\n";
    cout<<"Contents: "<<pkt.contents<<"\n";
}

In the above example the function would be invoked like a normal function:

    debugPrint(packet);

If the function is defined as specialized function, then defining the function as template can be avoided:

void debugPrint(Packet<int> pkt)
{
    cout<<"Msg Id: "<<pkt.message_id<<"\n";
    cout<<"Contents: "<<pkt.contents<<"\n";
}

However, by using the above approach the flexibility of templates is lost.

Default type argument

Default values for type parameters in templates can be defined. In the following example the default value for type parameter T is defined as int

#include <iostream>
using namespace std;
template <typename T =int>
class Message
{
    public:
        T contents;
        Message(T contents)
        {
            this->contents = contents;
        }
};
int main()
{
    Message<> msg(10);
    cout<<msg.contents<<endl;
    return 0;
}

Since the default value for type parameter is defined as int, so the instance of Message class is defined as Message <> instead of Message <int>.

Usual rules of default function parameters apply to default type parameters of templates as well.

Default value for type parameters is allowed only for class templates, but not for function templates.