TEMPLATES

Function templates

Selection sort, as written, does not rely on the elements of data being of type int, it only requires that the elements of data be comparable (i.e. have a < operator defined for them), allow them to b initialized (think default constructor) and assigned to. Integers have these properties, but so do a host of other types.

Goal: parameterize sort so that it can operate on arrays of any types which support those basic operations.

How do we do this? A simple idea is to use the typedef mechanism. The statement:

typedef t foo
  
makes foo a synonym for the type t (whatever type t is). In our case, we might write:
typedef int ElementType
  
and then rewrite sort using ElementType in place of some or all of occurrences of int.

Now if we want to change this sort function to operate on arrays of strings instead, we just change one line:

typedef String ElementType
  

This has its limitations: what happens if we want to use an int sort in one part of our program and a String sort in another?

A more flexible solution is to use function templates. A template is, like it sounds, a form where "blanks" get filled in at a later date. For sort, template function looks like:

template <class T>
void SelectionSort(T A[], int N)
{
  T min;
  int j, k, minIndex;

  for (k = 0; k < N; k++) {
     min = A[k]; minIndex = k;
     for (j = k+1; j < N; j++) {
        if (A[j] < min ) { min = A[j]; minIndex = j; }
     }
     Swap(A[k], A [minIndex]);
  }
}

How do templates work? Each time a template function is called - the types of the arguments to the function are plugged into the template. The compiler generates one version (an instantiation) of the template function for each set of types used when calling the template function. For example, consider the power function:

template <class Base>
Base power(Base b, int n)
{
  B result = b;
  for(; n > 0; n--)
    result = result * b;
  return result;
}

power(10,4) // uses int power
power(3.14,2) // uses double power
power(Complex(0,1),2) // uses Complex power, assuming 
power("hello",2) // error - 
                 // string has no multiplication operator defined

How should function templates be written?

  1. Write a specific version of the function with fixed types.
  2. Compile and test the function.
  3. Replace selected types with type parameters and add template header

A caveat: Each template type parameter must appear in one of the function specifications (not including return type). If not, compiler does not know how to "resolve" the type parameter. For example:

// valid:
template <class T>
void swap(T& A, T& B)
{
  T temp;

  temp = A;
  A = B;
  B = temp;
}


// invalid:
template <class T>
T donothing()
{
  T temp;

  temp = A;
  A = B;
  B = temp;
}

Template type parameter resolution

  1. Check to see if there are any non-template versions of the function that match the types precisely.
  2. Check to see if there are any template versions that match the type precisely - there must be an exact match on all the arguments to the function.
  3. Use "overload resolution" to check if any non-template functions can be coerced into matching the type.

Class templates

When discussing bags, we have focused on a Bag class that has elements of type int. We observed that nothing in our implementation of the Bag class depends on int. When discussing lists, we have focused on the StringList class - a list of strings. Likewise, this class is not dependent on the type String. We would like to be able generate a bag of strings, a bag of ints, a bag of int bags, a bag of string lists, and bags of items of virtually any type all from from the same Bag code. We would also like to be able to generate a list of ints, a list of string lists, a list of int list bags, etc. all from the same List code.

How can we accomplish this? Like in the case of functions that can be generalized to operate on a variety of types, classes can be parameterized in several ways:

Member functions for class templates

Member functions for class templates are themselves function templates. For example:

template <class Item>
bool Bag<Item>::isEmpty() const
{
  return (used==0);
}
and
template <class Item>
Item List<Item>::current() const
// Returns the item pointed to by the cursor 
{
  assert(!isEnd());

  return location->val;
}

Writing class templates

As when writing function templates, it is better to write the class for a fixed type first (perhaps using typedefs). Compile and test your implementation thoroughly before converting it to a class template.

Instantiating class templates

A class template is not a class (nor a valid type). Thus if List is a class template, we cannot write:

List L;
  
(However, List() remains the name of the constructor for all instantiations of the List class template.)

We can write expressions of this form:

List<int> iL;
List<List<String>> sLL;
but not:
List<List> LL;
However, we can combine the use of typedefs with class templates to useful effect:
typedef List<int>IntList;
typedef List<IntList> ListList;

A quirk of the design of C++ requires that when using either kind of template (function or class), the implementation of that template must be visible at to the compiler at its use. The templates can still be kept in other files, but #include must be used to keep things visible.