Templates

Programming Workshop 2 (CSCI 1061U)

Faisal Qureshi

Faculty of Science, Ontario Tech University

http://vclab.science.uoit.ca


Introduction

Templates are an important feature of C++. C++ std library and other libraries rely heavily upon templates.

I provide some examples of templates in different contexts (functions, classes, and templates with dynamic memory) at the end of this document.

Key idea

Templates 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

Examples

Functions

void swap(int& a, int& b)
{
    int tmp;
    tmp = a;
    a = b;
    a = tmp;
}

And the template version is

template<class T>
void myswap(T& a, T& b)
{
  T tmp;
  tmp = a;
  a = b;
  b = tmp;
}

Or we can use typename instead of class

template<typename T>
void myswap(T& a, T& b)
{
  T tmp;
  tmp = a;
  a = b;
  b = tmp;
}

It is also to mix and match multiple types as follows

template<typename A, typename B, typename C>
void threetypes(A a, B b, C c)
{
    // do something here
}

We can use the above template functions in our code as follows:

// fun.cpp
#include <iostream>
#include <string>
using namespace std;

namespace fq
{

template<class T>
void swap(T& a, T& b)
{
  T tmp;
  tmp = a;
  a = b;
  b = tmp;
}

}

int main()
{
  {
  int a = 1;
  int b = 2;
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  fq::swap<int>(a, b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  }
  
  {
  string a = "Hello";
  string b = "World";
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  fq::swap<string>(a, b); 
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  }
  
  return 0;
}

In the above code we place swap within fq namespace to avoid a conflict with the swap function in the std namespace.

Classes

Consider the following Pair.h file

#ifndef __Pair_h__
#define __Pair_h__

#include <iostream>
using namespace std;

template<class T, class U>
class Pair
{
  private:
  T first_;
  U second_;

  public:
  Pair();
  Pair(T first, U second);
  void setFirst(T first);
  void setSecond(U second);
  T getFirst();
  U getSecond();
  void prn();
};

template<class T, class U>
void Pair<T,U>::prn()
{
  cout << "First = " << first_ << endl;
  cout << "Second = " << second_ << endl;
}

template <class T, class U>
Pair<T,U>::Pair(T first, U second)
{
  first_  = first;
  second_ = second;
}

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

template<class T, class U>
void Pair<T,U>::setSecond(U second)
{
  second_ = second;
}

template<class T, class U>
T Pair<T,U>::getFirst()
{
  return first_;
}

template<class T, class U>
U Pair<T,U>::getSecond()
{
  return second_;
}

#endif

Template class body typically follows the class definition in the header file.

See how we use it below

#include "Pair.h"
#include <string>

typedef Pair<std::string, string> string_string_pair;

int main()
{
  Pair<int,char> p(1, 'a');
  p.prn();

  Pair<std::string, int> p1("World", 1);
  p1.prn();

  string_string_pair p2("Hello", "World");
  p2.prn();
  
  return 0;
}

Notice how we use typedef above to define Pair<string,string>.

Example: A more complicated template class

The following code sets up a simple array class (see ArrT.h).

#ifndef __ArrT_h__
#define __ArrT_h__

#include <iostream>
using namespace std;

template<class T>
class ArrT
{
  private:
  T* mem_;
  int n_;

  public:
  ArrT(int n);
  ~ArrT();

  T& operator[](int i);
  const T& operator[](int i) const;

  void prn();
  int size() const { return n_; }
  
  friend ostream& operator<<(ostream& o, const ArrT<T>& a)
  {
    for (int i=0; i<a.size(); ++i) {
      o << a[i] << endl;
    }
    return o;
  }
};


template<class T>
ArrT<T>::ArrT(int n) : n_(n)
{
  mem_ = new T[n];
}

template<class T>
ArrT<T>::~ArrT()
{
  delete[] mem_;
}

template<class T>
T& ArrT<T>::operator[](int i)
{
  return mem_[i];
}

template<class T>
const T& ArrT<T>::operator[](int i) const
{
  return mem_[i];
}

template<class T>
void ArrT<T>::prn()
{
  for (int i=0; i<n_; ++i)
  {
    cout << mem_[i] << endl;
  }
}

#endif

And the corresponding main file is:

#include <iostream>
#include <string>
using namespace std;
#include "ArrT.h"

int main()
{
  ArrT<char> ac(3);
  ac[1] = '1';
  ac.prn();

  ArrT<float> af(3);
  af[1] = 34.4;
  af.prn();
  cout << af << endl;

  ArrT<string> as(3);
  as[1] = "Marty";
  as.prn();
  cout << as << endl;

  
  return 0;
}

Caveats: which template parameters can be used?

Lets again consider our Pair class

#ifndef __Pair_h__
#define __Pair_h__

#include <iostream>
using namespace std;

template<class T, class U>
class Pair
{
  private:
  T first_;
  U second_;

  public:
  Pair();
  Pair(T first, U second);
  void setFirst(T first);
  void setSecond(U second);
  T getFirst();
  U getSecond();
  void prn();
};

template<class T, class U>
void Pair<T,U>::prn()
{
  cout << "First = " << first_ << endl;
  cout << "Second = " << second_ << endl;
}

template <class T, class U>
Pair<T,U>::Pair(T first, U second)
{
  first_  = first;
  second_ = second;
}

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

template<class T, class U>
void Pair<T,U>::setSecond(U second)
{
  second_ = second;
}

template<class T, class U>
T Pair<T,U>::getFirst()
{
  return first_;
}

template<class T, class U>
U Pair<T,U>::getSecond()
{
  return second_;
}

#endif

If we use it as follows:

// pair_main.cpp
#include "Pair.h"
#include <string>
#include <vector>

typedef Pair<std::string, std::string> string_string_pair;

int main()
{
  Pair<int,char> p(1, 'a');
  p.prn();

  Pair<std::string, int> p1("World", 1);
  p1.prn();

  string_string_pair p2("Hello", "World");
  p2.prn();

  std::vector<int> v;
  v.push_back(1);
  Pair<int, std::vector<int> > p3(1, v); // Problematic statement!
  p3.prn();

  
  return 0;
}

The compiler will complain. That’s because std::vector<int> conflicts with the cout << "Second = " << second_ << endl; statement in the following function.

template<class T, class U>
void Pair<T,U>::prn()
{
  cout << "First = " << first_ << endl;
  cout << "Second = " << second_ << endl;
}

What this means that only those template types are allowed that “support” all the functionality that is asked of them in the template body.

Example 1: This example showcases an issue an “invalid” type is used in a template

The following code will give a compile error since class A doesn’t support + operation that is used in the addition function.

#include <iostream>
using namespace std;

template<class T>
T addition(const T& a, const T& b)
{
    T c = a + b;

    return c;
}

class A
{
  private:
  int v_;

  public:
  A() : v_(0) {}
  A(int v) : v_(v) {}
  int& value() { return v_; }
  const int& value() const { return v_; }
};

int main()
{
  int a = 1;
  int b = 2;
  int c = addition<int>(a, b);
  cout << "c = " << c << endl;

  A a1;
  A a2(3);
  cout << addition<A>(a1, a2).value() << endl;
    
  return 0;
}

Solution 1: Fix class A to add a + operator.

We can fix it by providing a + operator for class A.

#include <iostream>
using namespace std;

template<class T>
T addition(const T& a, const T& b)
{
    T c = a + b;

    return c;
}

class A
{
  private:
  int v_;

  public:
  A() : v_(0) {}
  A(int v) : v_(v) {}
  int& value() { return v_; }
  const int& value() const { return v_; }

  // See here
  friend A operator+(A a, A b) 
  {
    A c(a.value() + b.value());
    return c;
  }
};

int main()
{
  int a = 1;
  int b = 2;
  int c = addition<int>(a, b);
  cout << "c = " << c << endl;

  A a1;
  A a2(3);
  cout << addition<A>(a1, a2).value() << endl;
    
  return 0;
}

Solution 2: Create a template specialization to handle class A differently.

Or we can create a specialization for addition method that handles class A differently. See below for more about specializations.

#include <iostream>
using namespace std;

template<class T>
T addition(const T& a, const T& b)
{
    T c = a + b;

    return c;
}
// And this is a specialization for addition() for class A.
// Notice how it doesn't need class A to have a + operator.
template<>
A addition(const A& a, const A& b)
{
    return A(a.value() + b.value());
}

class A
{
  private:
  int v_;

  public:
  A() : v_(0) {}
  A(int v) : v_(v) {}
  int& value() { return v_; }
  const int& value() const { return v_; }
};

int main()
{
  int a = 1;
  int b = 2;
  int c = addition<int>(a, b);
  cout << "c = " << c << endl;

  A a1;
  A a2(3);
  cout << addition<A>(a1, a2).value() << endl;
    
  return 0;
}

Template specialization

Taken from http://en.cppreference.com/w/cpp/language/template_specialization

Any of the following can be fully specialized:

Consider the code below

// spec.cpp

#include <iostream>

template<typename T>   // primary template
struct is_void : std::false_type
{
};

template<>  // explicit specialization for T = void
struct is_void<void> : std::true_type
{
};

int main()
{
    // for any type T other than void, the 
    // class is derived from false_type
    std::cout << is_void<char>::value << '\n'; 
    
    // but when T is void, the class is derived
    // from true_type
    std::cout << is_void<void>::value << '\n';
}

Example: Specialization of Template Functions

The following source code showcases the how we are able to specialize a template function.

#include <iostream>
#include <string>
using namespace std;

namespace fq
{

template<class T>
void swap(T& a, T& b)
{
  T tmp;
  tmp = a;
  a = b;
  b = tmp;
}

// Below we specialize the swap method for 
// std::string type
template<>
void swap(std::string& a, std::string& b)
{
  std::string tmp;
  tmp = a;
  a = b;
  b = tmp;
  cout << "Specialized swap called." << endl;
}

}

int main()
{
  {
  int a = 1;
  int b = 2;
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  fq::swap<int>(a, b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  }
  
  {
  string a = "Hello";
  string b = "World";
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  fq::swap<string>(a, b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  }

  return 0;
}

References