13.2. Template Functions

Time: 00:05:56 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

In general, templates are patterns, models, or outlines. An example of a template is a word processing file containing the basic format of a specialized document that writers use as a starting point. Writes use the template by filling in some specific or missing information. C++ template functions similarly provide the function's basic pattern or format while allowing programmers to provide one or more unspecified data types.

For example, the steps for swapping two array elements (a sub-task of sorting an array) are always the same, independent of the element type. The first presentation of the swapping problem demonstrated that a solution requires a temporary variable, whether implemented "inline" or as a function, requiring a data type as part of its definition. A function implementation further requires two parameter definitions, including their types. A large set of overloaded functions can handle the fundamental data types (char, double, etc.) but not application classes, structures, or enumerations. Templates allow programmers to create a generic swap function.

template <class T>
void swap(T& x, T& y)
{
	T temp = x;
	x = y;
	y = temp;
}
template <typename T>
void swap(T& x, T& y)
{
	T temp = x;
	x = y;
	y = temp;
}
double	data[1000];
	. . .
swap(data[i], data[j]);
---------------------------------
Person	people = new Person[100];
	. . .
swap(people[x], people[y]);
(a)(b)(c)
Defining and calling a template-based swap function. The yellow-highlighted text is "boilerplate code" signaling the beginning of a template function definition to the compiler and introducing the template variable, T. The capital letter T is the traditional template variable when there is only one. T is a placeholder for some general, yet-to-be-determined data type. The data type is determined later when the compiler compiles the application code using the function, replacing the T with a fundamental or program-created data type.
  1. The original, still valid syntax, used the class keyword to begin template functions.
  2. Modern C++ adds the typename keyword to the templating mechanism, making both keywords valid.
  3. The compiler determines the replacement type from the application program's data.
    • In the first (light blue) example, the application passes two elements from a double array, replacing T with double.
    • The second (light red) example assumes that Person is an application class. So, passing two elements of the people array replaces T with Person.

How C++ compiles template functions differs from how it processes "regular" or non-template functions. The compiler compiles regular functions to machine code, which the linker or loader can link to one or more application programs in the future. However, when substituting a type name for the template variable, the compiler can't make the substitution independently of the application using it, implying that the compiler must compile the template function and application code simultaneously. Therefore, it's impossible to compile template functions and store the object code in a system library like sqrt and pow. C++ provides template functions as source code in header files, which application programs #include, so they are compiled with the application.

C++ libraries put template function source code in a header file, programmers include it with "normal" source code, and the compiler processes it following the type expansion or substitution. If the application uses the template function with multiple data types, each type results in a different substitution, one for each type. For example, Figure 1(c) shows the swap function used with both an array of integers and an array of Person objects. In the first example, int replaces T, while in the second example, Person replaces it. Each replacement results in a separate copy of the swap function source code in the program, one copy processing int values and the second processing Person objects.

<algorithm>: The Template Function Library

C++ provides numerous template functions, prototyped in the <algorithm> header file. There are too many to describe in detail, so we focus on one as an example. The min function (near the bottom of the list) is just one of many template functions C++ programmers may utilize. The complexity and flexibility of these functions have steadily increased since their introduction with the ANSI 1998 C++ standard.

template <class T>
const T& min(const T& a, const T& b)				// (a)
{
    return (a < b) ? a : b;
}

template <class T, class Compare>
const T& min(const T& a, const T& b, Compare comp)		// (b)
{
    return comp(a, b) ? a : b;
}
<algorithm> min template functions. The <algorithm> library has three min template functions. The first function utilizes a single template variable to handle naturally orderable types. The second function utilizes two template variables, supporting a broader range of types, including those not intrinsically orderable. The third prototype allows programmers to use initializer lists, which are less common in this context and skipped for brevity.
  1. To use the first, simple version1, the compared values must have a "natural" ordering. The fundamental types satisfy this requirement: 5 is "naturally" less than 10, 'A' is less than 'B,' and, oddly, in C++ false is less than true. An overloaded operator< imposes a "natural" ordering on instances of the overloading class, satisfying the "natural" ordering requirement for this version.
  2. The second version adds a third parameter, called a comparator, implementing an arbitrary programmer-defined ordering and allowing applications to use the min function with objects without a natural ordering. Furthermore, applications can define multiple comparators, allowing them to specify various orderings at different times in the program. This version introduces a second template variable, Compare, to represent the comparator's data type. Applications can pass the comparator as a function pointer or an external object. The following examples demonstrate both options.

<algorithm> Examples

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

int main()
{
	int	x = 20;
	int	y = 10;

	cout << min(x, y) << endl;

	return 0;
}
The min template function and fundamental data types. The template function min, prototyped in the <algorithm> header file, detects the arguments are type int and automatically returns an int. The fundamental data types inherently define their natural ordering.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

class Employee
{
    private:
	string	name;
	int	id;

    public:
	Employee(string n, int i) : name(n), id(i) {}

	friend	bool	 operator<(const Employee& e1, const Employee& e2) { return e1.name < e2.name; }

	friend	ostream& operator<<(ostream& out, const Employee& me)	// (a)
	{
		out << me.name << "\t" << me.id;
		return out;
	}
};

int main()
{
	Employee	e1("Dilbert", 123);
	Employee	e2("Alice", 987);

	cout << min(e1, e2) << endl;					// (b), requires "const" in operator<<
	Employee	e3 = min(e1, e2);				// (c), works with and without "const"
	cout << e3 << endl;

	return 0;
}
The min template function and "natural" ordering (operator<). The Employee class has two member variables, implying there are two ways to order or compare two Employee objects: alphabetically (more accurately, "ACIIbetically") by name or numerically by id number. Programmers implement a class's "natural" order by overloading the "less than" operator. However, given two instances of the class as arguments, a class may only define one overloaded operator<, implying that a class can only have one "natural" ordering. This example orders two Employee objects by name.

Both versions of the min function (Figure 2 (a) and (b)) return a const value, complicating the coordination between operator<< and the min function. Making me const (a) enables statement (b) to compile and run but limits how programmers can use the Employee inserter. Conversely, if me isn't const (a), statement (b) fails. In this case, programmers can use statement (c), which works in both cases.

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

class Employee
{
    private:
	string	name;
	int	id;

    public:
	Employee(string n, int i) : name(n), id(i) {}

	friend	bool	 comp1(const Employee& e1, const Employee& e2) { return e1.name < e2.name; }
	friend	bool	 comp2(const Employee& e1, const Employee& e2) { return e1.id < e2.id; }

	friend	ostream& operator<<(ostream& out, Employee& me)
	{
		out << me.name << "\t" << me.id;
		return out;
	}
};

int main()
{
	Employee	e1("Dilbert", 123);
	Employee	e2("Alice", 987);

	Employee	e3 = min(e1, e2, comp1);
	cout << e3 << endl;

	Employee	e4 = min(e1, e2, comp2);
	cout << e4 << endl;

	return 0;
}
The min template function and comparator functions. The Employee class continues to have two data members, and application programs can order two Employee objects by either one. However, a class can only have one overloaded operator<, implying that a class can only have one "natural" ordering. Nevertheless, an application may need to order objects in various ways at different times. The three-parameter version of the min function, Figure 2(b), allows an application to pass a comparator as the third argument.

This example illustrates creating two comparator functions and passing them by pointer to min. One function, comp1, orders Employee objects by name and the other function, comp2, orders them by id number. Making the functions friends of the Employee class allows them to access its private members. Alternatively, application programmers can define the ordering functions in the application if the ordered objects provide appropriate getter functions. This example passes the functions by pointer.

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

class Employee
{
    private:
	string	name;
	int	id;

    public:
	Employee(string n, int i) : name(n), id(i) {}

	string	getName() const { return name; }
	int	getID() const { return id; }

	friend	ostream& operator<<(ostream& out, Employee& me)
	{
		out << me.name << "\t" << me.id;
		return out;
	}
};

class CompareName
{
    public:
	bool operator() (const Employee& e1, const Employee& e2) { return e1.getName() < e2.getName(); }
};

class CompareID
{
    public:
	bool operator() (const Employee& e1, const Employee& e2) { return e1.getID() < e2.getID(); }
};

int main()
{
	CompareName	c1;
	Employee	e1("Dilbert", 123);
	Employee	e2("Alice", 987);

	Employee	e3 = min(e1, e2, c1);
	cout << e3 << endl;

	return 0;
}
The min template function and comparator objects. Function objects, or functors are objects that behave like functions - they are instances of classes that define the function call operator, operator(). Comparators are a special case where the operator compares two objects and returns a value encoding their relative order. In general, they can return an integer encoding like the strcmp function. However, min function comparators must return a Boolean value or a value that can be converted unambiguously to a Boolean - true if the first argument comes before the second; false otherwise. In this example, an instance of the CompareName class is created and passed as the third argument to the min function. c1 is a comparator ordering two Employee objects by name; we could also create a new comparator ordering Employee objects by id.

  1. The textbook version, (a < b) ? a : b, and the linked online version, !(b < a) ? a : b, are similar but not identical. The difference between the two versions' tests (the conditional operator's first operand) is subtle and often irrelevant. The two versions behave differently only when a == b. In this case, the textbook version returns b while the online version returns a. But, since the two are equal, does it matter which value the function returns?

    Alone, returning b rather than a is irrelevant. However, the difference may become relevant when the min function is part of a more complex operation, such as sorting an array's elements. A sorting algorithm is said to be stable (an often desirable characteristic) if it doesn't change the relative order of equal elements. For example, if a precedes b in an unsorted array, a stable sorting algorithm will preserve that relative order in the sorted array. If the sorting function calls min, the online version maintains the sorting operation's stability, while the textbook's simpler version does not.