By Stephen R. Davis

C++ is an example of a strongly typed programming language. It’s this strong typed-ness that allows functions to be overloaded, as shown in the following code snippet:

void grade(Student&);
void grade(Land&);
void fn(Student& s)
{
    grade(s);
}

C++ has no problems understanding that the call to grade(s) should be directed to the function grade(Student&).

Strong typing forces methods to be overloaded that appear the same at the C++ level but are quite different in their implementation as in the following example:

int compare(int l, int r)
{
    return (l > r) ? 1 : (l < r) ? -1 : 0;
}
int compare(double l, double r)
{
    return (l > r) ? 1 : (l < r) ? -1 : 0;
}

This compare() returns a 1 if the left-hand argument is greater than the right-hand, a –1 if the left-hand argument is less than the right-hand, and a 0 if they are equal. Despite the similarity of the source code, the machine code generated by these two functions is very different, due to differences in the way that int is implemented versus double at the machine level.

The preprocessor offers one way to avoid such redundant implementations:

#define COMPARE(l, r) (((l)>(r))? 1: ((l)<(r)) ? -1 : 0)

However, this suffers from a number of serious limitations — not the least of which is the need for the function to fit in one expression (in effect, on one line). In addition, errors in a preprocessor macro can lead to problems that are difficult to debug.

Function templates

The template feature in C++ allows the programmer to retain the advantages of strong typing and avoid the limitations of the preprocessor by using a placeholder for the type. The following defines compare() for some unspecified class T to be named later:

template <class T> T compare(T l, T r)
{
    return (l > r) ? 1 : (l < r) ? -1 : 0;
}

Such a definition is known as a function template. This function template — whose full name is compare<T>(T,T) — can be converted into a real function by supplying T:

template double compare<double>(double, double);

You can also allow C++ to find and instantiate the class T for you, either by providing a partial declaration or by using the function as shown in the following example code snippet:

template double compare(double, double);
int main()
{
    cout << compare(0, 0) << " " 
         << compare(1.0, 2.0) << endl;
    return 0;
}

Here the function compare(double, double) is created by the initial declaration, even without the extra <double> after the function name. The function compare(int, int) is created when it’s used in the statement compare(0, 0).

Class templates

The C++ template feature allows classes to be defined using types that are specified later. This solves a number of different problems in a type-safe way. One problem, though far from the only problem, is that of generic containers. (A container is a class that holds objects of another class.)

In the early days of C++, the only way to create a generic container class was to rely on the generic void pointer (the pointer of class void*). Much like the #define “function” mentioned earlier, this approach effectively sidestepped C++’s strong typing mechanism with statements like the following:

class Container
{
  public:
    void add(void*);
    void* get();
    // ...other members...
};
Class container;
void storeStudent(Student& s)
{
    container.add((void*)&s);
}
Student* getStudent()
{
    return (Student*)container.get();
}

C++ templates allow the programmer to define a class template in which one or more types is not specified until the class is used:

template <class T> class Container
{
  public:
    void put(T* p);
    T*   get();
};

In practice, the class template must first be converted into a real class by supplying a type for T. It may then be used in full type safety:

Container<Student> container;
Student s;
container.put(&s);
Student* pS = container.get();

There is no need to define your own container class; the Standard Template Library provides a number of such classes. For example, the following code snippet creates a linked list of pointers to Student objects and adds one to the end of the list:

// must #include <list> at beginning of module
list<Student*> sList;
Student* pS = new Student();
sList.push_back(&s);

Many of the classes that you use every day are, in fact, instantiations of class templates. The most common example is when istream and ostream are used for standard input and output.

Iterators

Although they’re not directly part of the template feature, iterators provide a standard way to access the different types of containers available in the Standard Template Library. All containers provide a method that returns an iterator to the beginning of the container and a way of checking when the iterator is at the end of the container (this includes unordered containers for which ‘beginning’ and ‘end’ is a random concept). The iterator itself provides at least two methods: one to return the current object and one to avoid the iterator to the next object.

The following code iterates through a collection of pointers to Student objects. This same code works irrespective of which one of the many types of containers you choose to use:

// displayAllStudents - iterate through a list of
//                      Students; invoke the toString()
//                      method on each
void displayAllStudents(list<Student*>& sList)
{
    for(auto iter = sList.begin();
             iter != sList.end();
             iter++)
    {
        Student *pS = *iter;
        cout << pS->toString() << endl;
    }
}

(This example assumes that the class Student includes a method toString() that returns a character representation of a student.)

This function uses the begin() method to return an iterator that points to the first member of the container. The function iterates through the container until the iterator points to end() which refers to the member after the last member in the container. The increment operator moves the iterator to the next member in the container while the * operator returns the object pointed at by the iterator.

The auto keyword says declare iter to be of the type returned by sList.begin(). This is an extension to C++ added by the 2011 standard. Without auto, I would have declared iter to be type list<Student*>::const_iterator.