Essentialssec15.htm

Essentials of C++: Section XV


Templates
To support software reuse, C++ includes templates or parameterized types. These allow you to declare one general function or class description to manipulate different types as long as the manipulations are the same for each type. Examples include:
  • one general function to perform comparisons on two values of any type as in two integers, two doubles or two instances of a class;

  • one general class to represent a list of some type such as a list of integers, a list of doubles, or a list of some class type.

A. Function Templates

A function template is a general description that will be used to create specific instances of the function - where the specific instances are called template functions. Each general description begins with a template header with the form:

template <one or more of the pair: class some_type>

Examples:

template <class Typ>

template <class Typ1, class Typ2)

The first example says that the function definition to follow will use one type parameter while the second one will use two type parameters. It is these parameters that determine what types a specific function formed from this general type will use.

To define the function template one then adds what looks like a standard function definition except that it uses the type parameters found in the template header. For example, to declare a function template that compares two elements of the same type and return the larger:


	template <class Typ)
	Typ Compare(Typ elem1, Typ elem2)
	{
		if (elem1 > elem2)
		{	
			return elem1;
		}
		else
		{
			return elem2;
		}
	}
When this function is called, the types of the parameters used in the function call determine the types in the specific template function created. Since the types of parameters are known at compile time, it is the compiler that creates the different instances of the general function description. If 'Compare' is called one or more times with integer parameters, one or more times with double parameters, and one or more times with parameters of some class type, the compiler will create three instances of the function 'Compare'. Which version is called each time is then handled using the same approach used for function overloading. In fact, one can see this as a form of compiler generated function overloading.

The programmer could accomplish the same goal using three explicitly defined overloaded functions but then the burden of work falls on the programmer. Function templates allow the programmer to declare a single, general definition of a function as long as all types using this general definition require the same code. One can mix overloading and function templates in cases where some types require special code. The compiler will first try to find specific function matches and only then will it work with the function template.

All the type parameters in the template header must be included in the function definition. In addition, if a class type is passed as an actual parameter, any operators used in the function definition must be defined for the class type. For example, in the above code, if 'Compare' is called with two elements of type 'Class1' then the '>' operator better be defined for 'Class1'.

It is perhaps unfortunate that the word 'class' is used in the function header. It has nothing to do with the object oriented programming notion of class and simply states that the parameter name that follows represents a type - any type, built-in or user defined.
 

B. Class Templates
When different types all use a common class pattern, as in a 'container' class that holds a set of elements of some type and performs insert, delete, print etc operations on the elements in the 'container', a class template is useful. As with function templates, class templates can be seen as general descriptions of a class pattern that can be used to create specific instances of the pattern, referred to as template classes.

Consider a queue, a class representing an arrangement of elements where elements are deleted from one end and added at the other - as in a line or queue at a theatre. In general, one can have a queue of persons, animals, processes (in an operating system) etc. To declare a template for this class, one starts with the same header form as with function templates:

template <one or more of the pair: class some_type>

followed by the class declaration, as in:


	template <class Typ>
	class Queue
	{public:
		Queue(int sz);
		~Queue ();
		Add(Typ elem);
		Typ Delete();
		   // other member functions

	 private:
		int sizeOfQueue;
		int front;
		int rear;
		Typ* queue;
	};

In this class declaration the type parameter, 'Typ' is used to indicate:
  • that the queue will hold an array of elements of type 'Typ':
    Typ* queue;
  • the type of element to be added to the queue
    Add(Typ elem);

  • the type of element to be deleted from the queue and returned:
    Typ Delete();

To declare a specific type of queue class to contain doubles, one could write:

Queue<double> queueForDoubles(50);

Upon seeing this, the compiler creates a class we might call 'QueueOfDoubles' and instantiates the instance 'queueForDoubles' to hold up to 50 doubles.

To define the member functions of a class template, one uses the class template's header followed by a slightly modified version of the standard code for member functions. For example, here is the code for the three member functions for Queue:


	template <class Typ>
	Queue<Typ> :: Queue (int sz)
	{	sizeOfQueue = sz;
		queue = new Typ [size];
		front = 0;
		rear = 0;

	}

	template <class Typ>
	Queue<Typ> :: ~Queue ()
	{	
		delete []  queue;	
	}

	template <class Typ>
	Queue<Typ> :: Add(Typ elem)
	{ 	if (rear < size)  // simplified to ignore issues of circular
                                     // queues etc.
		{	queue[rear] = elem;
			rear++;
		}
		else
		{	
			cout << "\nNo room to add\n";
                        // better if queue returned success or failure
		}
	}

	template <class Typ>
	Typ Queue<Typ> :: Delete()
	{	if (front < rear) // simplified to ignore issues of
		                     // circular queues etc.
		{	return queue[front--];
		}
		else
		{	cout << "\nQueue is already empty\n";
		          // better if queue returned success or failure
		}
	}
This code does not handle the intricacies of queues but it does demonstrate how to define class template member functions. Note that for each member function the template header is repeated and then the class name followed by angle brackets,<....>, with the type parameter(s) inside the brackets. When different template classes are created from a single general form, the information in the brackets will allow the compiler to keep the instances and their associated member functions separate from each other.

It is also legal to include non-type parameters as arguments for the template header. For example, if one wanted to allow the user of the class template to declare classes of queues with specific sizes,one could write:


	template <class Typ, int size>
	class Queue
	{public:
		Queue();
		~Queue ();
		Add(Typ elem);
		Typ Delete();
		  // other member functions

	 private:
		int sizeOfQueue;
		int front;
		int rear;
		Typ* queue;
	};


The constructor would no longer receive the size and declarations of specific queues would take the form used in the following example:

Queue<double, 50> queueForDoubles;

Upon seeing this, the compiler creates a class we might call 'QueueOf50Doubles' that will hold 50 doubles and instantiates the instance 'queueForDoubles'. The difference between this and the early declaration is that this time it is the class QueueOf50Doubles' that determines the size of the queue, not the instantiation of the specific instance.

There are a number of issues related to friends and class templates as well as static members of class templates. You should consult other books and your manual for more information. C++ How to Program by H.M. Deitel and P.J. Deitel provides a brief but good introduction to these two topics.

C. Class Templates and File Linkage
As noted above, when an instance of a template class is instantiated, as in the queue of doubles, the compiler actually creates a new class. If a second instance of this same type of queue is also instantiated, there is no need for a new class. However, if an instance of 'Queue' is later instantiated that holds integers, a new class is created by the compiler. Each type of queue has its own class, with its own set of member functions. In the case of the 'Queue' class, each type of 'Queue' will have its own constructor, destructor, Add, and Delete member functions.

Herein lies a problem with the normal way we compile and link code. When we compile the .cpp file containing the definitions for the 'Queue' class, there is normally no information given to the compiler about how these definitions will be used. In our example, then, the compiler is not told that there will be one or more queues of integers and one or more queues of doubles. Thus, the compiler has no idea that it should define member functions for a queue of doubles and a separate set of member functions for a queue of integers.

However, when the compiler works on a program file with instance declarations for queues of integers and queues of doubles, it does assume the existence of the appropriate member functions. The compiler makes this assumption, of course, because it has access (via an #include statement) to the header file containing the declarations for class 'Queue'. It sees the declaration and fills in the appropriate values for the type parameters. In other words, it interprets the header file as a 'promise' that the appropriate function definitions will be provided later.

It is at link time that the problem occurs. No such functions are found and one receives the type of error message one gets when forgetting to define a function or misspelling its name in the definition file.

There are two solutions to this problem. One is to ignore the principles behind separate compilation of files and include the code for the member functions in the header file for the class declaration. Now, when the declaration file is included in the program file, the member function definitions end up in the same file as the instance declarations. Since the compiler sees both the instance declarations and the generic member functions at the same time, it knows what type of specific member functions to create. In our example, if the program file ends up with the generic member function definitions and instance declarations for "queues of doubles" and for "queues on integers", it will create a set of queue member functions to handle doubles and a set of queue member functions to handle integers.

The second solution involves the use of special compiler 'switches'. What follows is the approach used in Borland C++.

In the program file, include, towards the top, the line:

#pragma option –Jgx

This tells the compiler to expect the template instance to be defined elsewhere. This is an extension of the use of the keyword 'extern'. Then, in the file containing the class definitions, include the line:

#pragma option –Jgd

Follow this line with code that lists all the specific types of instances that need to be created. If one wants queues of integers and queues of doubles, the code would be:

typedef Queue version1;
typedef Queue version2;

The 'Jgd' tells the compiler to generate public definitions accessible from other files for each of the instance types listed. In other words, the scope of the member functions of both class instances includes any file with the –Jgx switch.

The two 'typedef' lines inform the compiler that it is to create member functions for both a "queue of doubles" class and a "queue of integers" class. The identifiers 'version1' and 'version2' are simply placeholders. They are necessary for the syntax of 'typedef' but do nothing and are not used.

Templates can simplify the work of a programmer but only at a price. Much has been skipped. Specifically, there are a number of issues related to friends and class templates as well as static members of class templates. You should consult other books and your manual for more information. C++ How to Program by H.M. Deitel and P.J. Deitel provides a brief but good introduction to these two topics.

Top of Section Main Menu of Essentials of C++