In general, a template is a pattern or model. An example of a template is a (word processing) file that contains the basic format of a specialized document that writers use as a starting point. We use the template by filling in some specific or missing information. A C++ template function similarly provides a function's basic pattern or format. We can use the function by providing (a) any unspecified data types and (b) any data values (i.e., function arguments).
For example, suppose that we need to write a swap function that swaps the values stored in two variables (this task is a vital sub-operation used by functions that sort data stored in an array). It would be nice if our function worked with all arrays regardless of the kind or type of data stored in the array. Based on C++ templates, we can write and use a general, type-independent swap function as illustrated in Figure 1:
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) |
class
to introduce the template variabletypename
to introduce the template variable because any data type, not just class names, can replace TTemplate functions share another important connection with parameterized macros: we must wait until the type substitution is complete before compiling them. It is impossible to compile template functions and store the object code in a system library like sqrt
and pow
. That doesn't mean that we can't or don't have libraries of template functions, but it does mean that template functions are provided as source code.
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. Furthermore, the compiler expands the template source code for each data type used with a template function. So, a program contains one copy of the code for each data type that the program needs the function to support. For example, Figure 1(b) shows the swap function used with both an array of integers and an array of person objects. In the first example, int replaces the function template variable, and in the second example, the class name person replaces the function template variable. Each replacement results in a separate copy of the swap function source code with int and person replacing the template variable T respectively.
C++ does provide a rich set of template functions, which are contained in the <algorithm> header file. 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. For example, two simple but quite useful versions of the min template function are:
template <class T> const T& min (const T& a, const T& b); template <class T, class Compare> const T& min (const T& a, const T& b, Compare comp);
The first function "assumes" that the type replacing T supports the < operator. So, if a class replaces T, the class must overload operator<. Figures 2 and 3 demonstrate this version of min. The second version of min uses an external comparator, which may be either a function pointer (demonstrated in Figure 4) or a function object (demonstrated in Figure 5).
#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, employee& me) { out << me.name << "\t" << me.id; return out; } }; int main() { employee e1("Dilbert", 123); employee e2("Alice", 987); //cout << min(e1, e2) << endl; // doesn't work employee e3 = min(e1, e2); cout << e3 << 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 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; }
#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; } 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(); } }; int main() { CompareName c1; employee e1("Dilbert", 123); employee e2("Alice", 987); employee e3 = min(e1, e2, c1); cout << e3 << endl; return 0; }