Templates

Programming Workshop 2 (CSCI 1061U)

Faisal Qureshi

Faculty of Science, Ontario Tech University

http://vclab.science.uoit.ca


Introduction

Allow generic definition of functions and classes.

Consider the swap() function below.

// swap() function that works for integers
void swap(int& i, int& j)
{
    int temp = i;
    i = j;
    j = temp;
}
// swap() function that works for floats
void swap(float& i, float& j)
{
    float temp = i;
    i = j;
    j = i;
}

Both of which have the exact same functionality. Wouldn’t it be nice to not have to duplicate functionality.

We can use C++ Templates to write a single swap function. The correct version will be generated by the compiler during compile time.

We can write a templated swap function as follows

template<class T>
void swapValues(T& variable1, T& variable2)
{
      T temp;

      temp = variable1;
      variable1 = variable2;
      variable2 = temp;
}

Declaring and defining template functions

template<class T> void showStuff(T stuff1, T stuff2, T stuff3);
template<class T> void showStuff(T stuff1, T stuff2, T stuff3)
{   
    cout << stuff1 << endl
         << stuff2 << endl
         << stuff3 << endl;
}

Calling template functions

showStuff<int>(1, 2, 3);

Compiler generates code for void showStuff(int, int, int) at compile time.

showStuff<float>(1, 2.4, 3);

Compiler generates code for void showStuff(float, float, float) at compile time.

Multiple type paramters

The following is allowed

template<class N, class M> ...

However, we cannot have unused types

Common use of templates

Template programming

Template classes

See the following, which defines a Pair class that can store a pair of (the same) items (of type T).

template<class T>
class Pair
{
public:
    Pair( );
    Pair(T firstValue, T secondValue);
    void setFirst(T newValue);
    void setSecond(T newValue);
    T getFirst( ) const;
    T getSecond( ) const;
private:
    T first;
    T second;
};

template<class T>
Pair<T>::Pair(T firstValue, T secondValue)
{
    first = firstValue;
    second = secondValue;
}

template<class T>
void Pair<T>::setFirst(T newValue)
{
    first = newValue;
}

template<class T>
T Pair<T>::getFirst( ) const
{
    return first;
}

In the above

We can declare objects as follows:

Pair<int> score;

or

Pair<char> seats;

or

Pair<std::string> first_last_names;

Template types can be uses anywhere standard types can. E.g., see below

int func(const Pair<int>& pair) { ... }

Notice that we have supplied type int in the above. This means that function func() only accepts const references of type Pair<int>.

If we want function func() to support all Pair<T> then we need a template function as follows

template<class T>
T func(const Pair<T>& pair) { ... }

Function func() now supports all Pair<T>.

Type definitions

It is also possible to define new “class type name” for representing specialized class template name as follows

typedef Pair<int> IntegerPair;

And instead of

Pair<int> pair1;

We can write

IntegerPair pair1;

Similarly,

typedef Pair<string> FirstLastName;
FirstLastName name1, name2, name3; 

It is common practice to define class type names for sake of code brevity.

Friend functions in template classes

It is very common to have friend functions in template classes.

Inheritance in template classes

Have I seen templates before?

Yes!

std::string is an alternate name for std::basic_string<char>

Which means we can do the following

Dynamic memory allocation

Here’s an example of dynamic memory allocation in template function

template<class T>
T* create_array(int n)
{
    T* a = new T [n];
    return a;
}

int main()
{
    double* a;
    int n = 5;
    a = create_array<double>(n);
    return 0;
}

Terminology

(advanced topic)

The following section is borrowed from “API design for C++” by Martin Reddy, 2011. See also 1.

template <typename T>
class Stack
{
public:
    void push(T val);
    T pop();
    bool isEmpty() const;
private:
    std:vector<T> mStack;
};

Template Parameters

These names are listed after the template keyword in a template declarations. As shown in the example, T is the single template parameter specified in the Stack class.

Template Arguments

These are substituted for template parameters during specialization. In our example, given a specialization Stack, the int is a template argument.

Instantiation

This is when the compiler generates a regular class, method, or function by substituting each of the template’s parameters with a concrete type. This can happen implicitly when we create an object based on a template or explicitly if we want to control when the code generation happens. For instance, the following code creates two specific stack instances and will normally cause the compiler to generate code for these two different types:

Stack<T> myIntStack;
Stack<T> myStringStack;

Implicit Instantiation

This is when the compiler decides when to generate code for our template instances. Leaving the decision to the compiler means that it must find an appropriate place to insert the code, and it must also make sure that only one instance of the code exists to avoid duplicate symbol errors. This is non-trivial problem and can cause extra bloat in our object files or longer compile time. Most importantly, implicit instantiation means that we have to include the template definitions in our header files so that the compiler has an access to the definitions whenever it needs to generate the instantiation code.

Emplicit Instantiation

This is when the programmer determine when the compiler should generate the code for a specific specialization. This can make for much more efficient compilation and link times because the compiler no longer needs to maintain bookkeeping information for all of its implicit instantiations. The onus, however, is then placed on the programmer to ensure that a particular specialization is explicitly instantiated only once. So, explicit instantiation allows us to move the template implementation into the .cpp file, and so hide from the user.

Lazy Instantiation

This describes the standard implicit instantation behavior of a C++ compiler wherein it will only generate code for the parts of a template that are actually used. Given the previous two instantiations, for example, if we never called isEmpty() on the myStringStack object, then the compiler would not generate code for the std::string specialization of that method. This means that we can instantiate a template with a type that can be used by some, but not all methods of a class template. If one method uses the >= operator, but the type we want to instantiate does not define this operator. This is fine as long as we don’t call the particular method that attempts to use the >= operator.

Specialization

When a template is instantiated, the resulting class, method, or function is called a specialization. More specifically, this is an instantiated (or generated) specialization. However, the term specialization can also be used when we provide a custom implementation for a function by specifying concrete types for all the template parameters. The following is called an explicit specialization:

tempate<>
void Stack<int>::push(int val)
{
    // integer specific push implementation
}

Partial Specialization

This is when we provide a specialization of the template for a subset of all possible cases. In other words, we specialize one feature of the template but still allow the user to specify other features. For example, if our template accepts multiple parameters, we could partially specialize it by defining a case where we specify a concrete type for only one of the parameters. In our Stack example with a single template parameter, we could partially specialize this template to specifically handle pointers(*) to any type T. This still lets users create a stack of any type, but it also lets us write specific logic to handle the case where users create a stack of pointers. This partially specialized class declaration looks like this:

template <typename T>
class Stack<T*>
{
public:
    void push(T* val);
    T* pop();
    bool isEmpty() const;
private:
    std:vector<T*> mStack;
};

Pros and Cons of Templates

Pros

Cons

References