13.2. Template Functions

template, template keyword, class keyword, typename keyword, template variable, template function, boilerplate code
Time: 00:05:56 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides: PDF, PPTX
Review

In general, s template is pattern, model, or outline. For example, word processors often include templates for various documents, such as resumes, business letters, and labels. The templates include the document format and common text. Writers use the template as a starting point, filling in the unique or missing information. Similarly, C++ template functions provide the function's basic pattern or format while allowing programmers to provide one or more unspecified data types as template variables.

For example, the process of swapping the values stored in two variables is independent of their type. Furthermore, sapping two array elements is a necessary step in many sorting algorithms, making a swap function desirable. A set of overloaded swap functions can handle fundamental data types (char, double, etc.) but not application classes or structures. C++ templates allow programmers to create a generic functions like swap.

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 template keyword signals the beginning of a template function and introduces one or more template variables. Programmers copy the "boilerplate code" (highlighted in yellow) directly to the beginning of a template function. T, the traditional template variable when there is only one, is a placeholder for some general, yet-to-be-determined data type. The compiler replaces it with a fundamental or program-specified data type during compilation.
  1. The original, still valid, syntax used the class keyword to begin template functions.
  2. Using "class" to create a non-member template function is potentially confusing, so C++ added the typename keyword, making both 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.

The C++ compiler processes template functions differently than "regular" or non-template functions. It compiles regular functions into machine code, which the linker or loader can later link to one or more application programs, enabling the distribution of functions in binary form. However, the compiler derives the data type that replaces the template variable from the client or application program calling the function. The type derivation delays code generation until the compiler merges the template function and the application program. Consequently, C++ must distribute template functions as source code.

C++ typically puts template function source code in header files, which application programs #include. This organization allows compilers to process template functions with the application code. If the application uses the template function with multiple data types, each type results in a distinct substitution. For example, Figure 1(c) shows the swap function processing integers and Person objects, resulting in two different instances or copies of the swap function in the program.

<algorithm>: The Template Function Library

template, <algorithm>, template function

C++ provides numerous template functions in the <algorithm> header file, too many to describe in detail. Taking the min function (near the bottom of the list) as an example, demonstrates the general template library function syntax. 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)
{
    return (a < b) ? a : b;
}
template <class T, class Compare>
const T& min(const T& a, const T& b, Compare comp)
{
    return comp(a, b) ? 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 (see functor), satisfying the "natural" ordering requirement for this version.
  2. The second version adds a third parameter, called a comparator or ordering function, 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

functor, template function, <algorithm>
#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 that 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 operator, operator(). Comparators are a special case in which the operator compares two objects and returns a value that encodes 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 versions' tests (the conditional operator's first expression) 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.