Function Templates with Multiple Arguments. Sams Publishing Object-Oriented Programming in C++
Διαφήμιση
Διαφήμιση
15 3087 CH14 11/29/01 2:16 PM Page 686
686
Chapter 14
When the compiler sees such a function call, it knows that the type to use is int
, because that’s the type of the argument int1
. So it generates a specific version of the abs() function for type int
, substituting int wherever it sees the name
T in the function template. This is called
instantiating the function template, and each instantiated version of the function is called a
template function. (That is, a template function is a specific instance of a function template.
Isn’t English fun?)
The compiler also generates a call to the newly instantiated function, and inserts it into the code where abs(int1) is. Similarly, the expression abs(lon1) causes the compiler to generate a version of abs() that operates on type long and a call to this function, while the abs(dub1) call generates a function that works on type double
. Of course, the compiler is smart enough to generate only one version of abs() for each data type. Thus, even though there are two calls to the int version of the function, the code for this version appears only once in the executable code.
Simplifying the Listing
Notice that the amount of RAM used by the program is the same whether we use the template approach or actually write three separate functions. The template approaches simply saves us from having to type three separate functions into the source file. This makes the listing shorter and easier to understand. Also, if we want to change the way the function works, we need to make the change in only one place in the listing instead of three places.
The Deciding Argument
The compiler decides how to compile the function based entirely on the data type used in the function call’s argument (or arguments). The function’s return type doesn’t enter into this decision. This is similar to the way the compiler decides which of several overloaded functions to call.
Another Kind of Blueprint
We’ve seen that a function template isn’t really a function, since it does not actually cause program code to be placed in memory. Instead it is a pattern, or blueprint, for making many functions. This fits right into the philosophy of OOP. It’s similar to the way a class isn’t anything concrete (such as program code in memory), but a blueprint for making many similar objects.
Function Templates with Multiple Arguments
Let’s look at another example of a function template. This one takes three arguments: two that are template arguments and one of a basic type. The purpose of this function is to search an array for a specific value. The function returns the array index for that value if it finds it, or –1 if it can’t find it. The arguments are a pointer to the array, the value to search for, and the size of the array. In main() we define four different arrays of different types, and four values to
15 3087 CH14 11/29/01 2:16 PM Page 687
Templates and Exceptions
687 search for. We treat type char as a number. Then we call the template function once for each array. Here’s the listing for
TEMPFIND
:
// tempfind.cpp
// template used for function that finds number in array
#include <iostream> using namespace std;
//--------------------------------------------------------------
//function returns index number of item, or -1 if not found template <class atype> int find(atype* array, atype value, int size)
{ for(int j=0; j<size; j++) if(array[j]==value) return j; return -1;
}
//-------------------------------------------------------------char chrArr[] = {1, 3, 5, 9, 11, 13}; //array char ch = 5; //value to find int intArr[] = {1, 3, 5, 9, 11, 13}; int in = 6; long lonArr[] = {1L, 3L, 5L, 9L, 11L, 13L}; long lo = 11L; double dubArr[] = {1.0, 3.0, 5.0, 9.0, 11.0, 13.0}; double db = 4.0; int main()
{ cout << “\n 5 in chrArray: index=” << find(chrArr, ch, 6); cout << “\n 6 in intArray: index=” << find(intArr, in, 6); cout << “\n11 in lonArray: index=” << find(lonArr, lo, 6); cout << “\n 4 in dubArray: index=” << find(dubArr, db, 6); cout << endl; return 0;
}
Here we name the template argument atype
. It appears in two of the function’s arguments: as the type of a pointer to the array, and as the type of the item to be matched. The third function argument, the array size, is always type int
; it’s not a template argument. Here’s the output of the program:
5 in chrArray: index=2
6 in intArray: index=-1
11 in lonArray: index=4
4 in dubArray: index=-1
14
15 3087 CH14 11/29/01 2:16 PM Page 688
688
Chapter 14
The compiler generates four different versions of the function, one for each type used to call it.
It finds a 5 at index 2 in the character array, does not find a 6 in the integer array, and so on.
Template Arguments Must Match
When a template function is invoked, all instances of the same template argument must be of the same type. For example, in find()
, if the array name is of type int
, the value to search for must also be of type int
. You can’t say int intarray[] = {1, 3, 5, 7}; //int array float f1 = 5.0; //float value int value = find(intarray, f1, 4); //uh, oh because the compiler expects all instances of atype to be the same type. It can generate a function find(int*, int, int); but it can’t generate find(int*, float, int); because the first and second arguments must be the same type.
Syntax Variation
Some programmers put the template keyword and the function declarator on the same line: template<class atype> int find(atype* array, atype value, int size)
{
//function body
}
Of course the compiler is happy enough with this format, but we find it more forbidding and less clear than the multiline approach.
More Than One Template Argument
You can use more than one template argument in a function template. For example, suppose you like the idea of the find() function template, but you aren’t sure how large an array it might be applied to. If the array is too large then type long would be necessary for the array size, instead of type int
. On the other hand, you don’t want to use type long if you don’t need to. You want to select the type of the array size, as well as the type of data stored, when you call the function. To make this possible, you could make the array size into a template argument as well. We’ll call it btype
:
15 3087 CH14 11/29/01 2:16 PM Page 689
Templates and Exceptions
689 template <class atype, class btype> btype find(atype* array, atype value, btype size)
{ for(btype j=0; j<size; j++) //note use of btype if(array[j]==value) return j; return static_cast<btype>(-1);
}
Now you can use either type int or type long
(or even a user-defined type) for the size, whichever is appropriate. The compiler will generate different functions based not only on the type of the array and the value to be searched for, but also on the type of the array size.
Note that multiple template arguments can lead to many functions being instantiated from a single template. Two such arguments, if there were six basic types that could reasonably be used for each one, would allow the creation of 36 functions. This can take up a lot of memory if the functions are large. On the other hand, you don’t instantiate a version of the function unless you actually call it.
Why Not Macros?
Old-time C programmers may wonder why we don’t use macros to create different versions of a function for different data types. For example, the abs() function could be defined as
#define abs(n) ( (n<0) ? (-n) : (n) )
This has a similar effect to the class template in
TEMPABS
, because it performs a simple text substitution and can thus work with any type. However, as we’ve noted before, macros aren’t much used in C++. There are several problems with them. One is that macros don’t perform any type checking. There may be several arguments to the macro that should be of the same type, but the compiler won’t check whether or not they are. Also, the type of the value returned isn’t specified, so the compiler can’t tell if you’re assigning it to an incompatible variable. In any case, macros are confined to functions that can be expressed in a single statement. There are also other, more subtle, problems with macros. On the whole it’s best to avoid them.
What Works?
How do you know whether you can instantiate a template function for a particular data type?
For example, could you use the find() function from
TEMPFIND to find a C-string (type char*
) in an array of C-strings? To see whether this is possible, check the operators used in the function. If they all work on the data type, you can probably use it. In find()
, however, we compare two variables using the equal-to (
==
) operator. You can’t use this operator with C-strings; you must use the strcmp() library function. Thus find() won’t work on C-strings. However, it does work on the string class because that class overloads the
== operator.
14
Λήψη
Διαφήμιση