Section 1 - Function Templates

1B.1.1 Why Templates Are Needed
You've probably had the experience of having written a perfectly good function like void mySwap(int a, int b) and used it in your main(). For example:
void mySwap(int &a, int &b) { int temp; temp = a; a = b; b = temp; }
Good, great, wonderful. Then, a couple days later you are working with strings. You have to write an entirely new mySwap() for strings. You say you need to swap doubles? Better write a third overload.
void mySwap(string &a, string &b) { string temp; temp = a; a = b; b = temp; } void mySwap(double &a, double &b) { double temp; temp = a; a = b; b = temp; }
1B.1.2 Function Templates (or is it Template Functions?)
The C++ standard template library (STL) allows us to create what is called a type parameter, which can act as a "stand in" for specific types like int or string. We will construct a function template that has each instance of the specific type we are abstracting (int, string, whatever) replaced by this symbolic type parameter. To write a function template use the following syntax:
template <typename T> void mySwap(T &a, T &b) { T temp; temp = a; a = b; b = temp; }
The letter T is just a type parameter and can be called anything you want; T is common, but in practice other symbols are used -- MyType, Object, Comparable, etc. The type parameter stands for any type that you might want to pass to the function template: int, string, iTunesEntry, Galaxy. In the function definition, just use T where you would normally have the type of your function parameter.
Once you have this defined, you can use it for any data type swap you wish. Watch:
#include <iostream> #include <string> using namespace std; // --------------- templates go first --------------- template <typename T> void mySwap(T &a, T &b) { T temp; temp = a; a = b; b = temp; } // --------------- main --------------- int main() { int x = 3, y = 5; string s1 = "three", s2 = "five"; cout << "Original x, y: " << x << " " << y << endl; mySwap(x, y); cout << "Swapped x, y: " << x << " " << y << endl << endl; cout << "Original s1, s2: " << s1 << " " << s2 << endl; mySwap(s1, s2); cout << "Swapped s1, s2: " << s1 << " " << s2 << endl << endl; return 0; } /* ---------------- Runs ------------------- Original x, y: 3 5 Swapped x, y: 5 3 Original s1, s2: three five Swapped s1, s2: five three Press any key to continue . . . ---------------------------------------- */
That was pretty easy, right? And, by the way, sometimes programmers, writers and instructors accidentally call them template functions. Accept either term.
1B.1.3 Implicit Assumptions In Function Templates (optional for now)
So, what are the limitations and precautions about using a function template?
The first thing to understand about function templates is that they are not functions. You can write a template, but if you don't call it from anywhere, no function is ever compiled. Here's an example. Say we want a function template that prints the name of an object, embellished with some fancy border. It will use the object's getName() method without caring about what exact class it is referencing. We follow the steps and create the function template.
template <typename T> void showNameInBorders(T &object) { cout << "----------------\n"; cout << object.getName() << endl; cout << "----------------\n\n"; }
If we throw that into any program it will compile. That is, the program will compile -- this code will not compile -- it will be ignored until and unless we actually use it. So there will be no errors. This is in contrast to an actual function which gets compiled whether or not it is invoked. Let's investigate this further. We throw up two classes with getName() methods and try passing objects of each class to showNameInBorders():
#include <iostream> #include <string> using namespace std; // --------------- templates go first --------------- template <typename T> void showNameInBorders(T &object) { cout << "----------------\n"; cout << object.getName() << endl; cout << "----------------\n\n"; } // ------------ class prototypes ---------------- class Doggy { private: string name; public: string getName() { return name; } bool setName(string name); }; class PartNumber { private: int name; public: int getName() { return name; } bool setName(int name); }; // --------------- main --------------- int main() { Doggy mikesDog; PartNumber inventoryThing; mikesDog.setName("Chloe"); inventoryThing.setName(1234); showNameInBorders(mikesDog); cout << endl; showNameInBorders(inventoryThing); return 0; } bool Doggy::setName(string name) { if (name.length() > 1000) return false; this->name = name; return true; } bool PartNumber::setName(int name) { if (name > 100000 || name < 0) return false; this->name = name; return true; } /* ---------------- Runs ------------------- ---------------- Chloe ---------------- ---------------- 1234 ---------------- Press any key to continue . . . ---------------------------------------- */
This is all good. Now, try calling it with a class in which there is no getName() method.
showNameInBorders("Hi Mom");
You will get a compiler error. But it will not be on the line above, it will be on the line within the function template:
cout << object.getName() << endl; Error 1 error C2228: left of '.getName' must have class/struct/union
This will confuse us as it will seem to indicate we made an error in our function defintion. We didn't. It was in the invocation, inappropriately made. Here's what's going on:
So in the first, working version of the program, there were two entire copies of showNameInBorders() function definitions that we didn't see. One was showNameInBorders(Doggy &obj) and the other was showNameInBorders(PartNumber &obj). Those two compiled fine, because obj.getName() made sense in each case. But in the last example we were asking for a third version of the function whose signature would be showNameInBorders(string &obj). The signature is fine, but when we got to the line obj.getName(), the compiler choked as we were asking for a getName() on a string object - there is no getName() in the string class.
The moral is this. You can write anything you want in a function template, but the more you expect of your type parameter, the more restricted will be the kinds of objects you can pass to it.
In the context of our original mySwap() method, the only thing we assumed about the object was that you could assign one object to another, something that is acceptable of all C++ types and classes. In other words, there were no restrictions at all, making the function template universally applicable.
1B.1.4 Comparable Objects (optional for now)
We used function templates in this first week's code in the persona of arraySort(). Within the file Foothill_Sort.h, where arraySort() is defined, is a helper function template called floatLargestToTop():
// returns true if a modification was made to the array template <typename Comparable> bool floatLargestToTop(Comparable data[], int top) { bool changed = false; // notice we stop at length -2 because of expr. k+1 in loop for (int k =0; k < top; k++) if (data[k+1] < data[k] ) { mySwap(data[k], data[k+1]); changed = true; } return changed; }
What do you notice about it? It assumes that we can use the < operator on two elements in the array (look at the if condition). This is fine for ints or doubles, but we want to pass it iTunesEntry objects. Is there a < defined for that class? There must be, and there is, or the programs wouldn't compile. A glance into iTunes.h will verify this, but this is the kind of thing that you need to think about when you define templates. If you forget to overload < for a class, you won't be able to use arraySort() on it, since arraySort() calls floatLargestToTop(), which requires the comparisons. Furthermore, the compiler error may be confusing, since it will point to a line in floatLargestToTop() which is not the problem.
In consideration of this fact, some authors use the type parameter name Comparable, rather than T, when they are defining function templates, to remind themselves and users that these template functions demand an operator<() to be defined for the objects that are passed as arguments.
By the way, if the function template assumes a lessThan() member function of its passed-in arguments, rather than the < operator, then those objects need to be in a class that, instead of overloading <, have a lessThan() method defined.
ImportantWhatever features the function template assumes about its type parameter T must be implemented in any class whose objects are sent to the function template.
I tried to find a short and less grammatically challenging version of this last sentence, but I failed. Consider this a test of English language comprehension.