template keyword, class keyword, typename keyword, template variable, template function, boilerplate codeIn 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) |
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.
class keyword to begin template functions.typename keyword, making both valid.double array, replacing T with double.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.
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) |
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int x = 20;
int y = 10;
cout << min(x, y) << endl;
return 0;
}
#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;
}
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;
}
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;
}
(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.