One of the design goals of C++ was to help the programmer avoid duplicating code unnecessarily. Function templates make it possible for you to design a function and then let the compiler create the code to implement that function for a specific type.
For example, if you write a squaring function template
template <class T> T square(T& value) { return value * value; }
you can write
int Int = 5; int IntSquared = square(Int);
or
float Float = 2.1; float FloatSquared = square(Float);
This allows you to write the function once, instead of writing two nearly identical functions: one for float values and one for int values.
However, for some types, there may be a special implementation
that's faster or more efficient. In this article, we'll
show how you can specialize a C++ function template to provide
custom functionality for a specific type.
For most situations, you'll be able to create function templates that define the correct behavior for any type. Unfortunately, there's usually an exception to every rule, and, similarly, there's usually one or two types for which a function template won't create appropriate code.
Consider the comparison function tem-plate below:
template <class T> int equal(const T& a, const T& b) { return a == b; }
For most of the built-in types (int, char, float, and so on), this comparison works correctly. (It may not be efficient to pass int or char values by reference, but it does work correctly.) Unfortunately, if you pass two C-style strings (arrays of char) to this function, it will compare the values of the pointers instead of comparing the strings.
In addition, if you call this function with two different but
compatible types, the compiler will consider it an ambiguous call.
To see why this happens, let's review how the compiler
creates the code for function templates.
When the Borland C++ compiler is scanning a source file and begins
processing a function call of a given name, it uses the following
process to locate and call the correct version of the function:
Since the compiler searches for existing functions before it tests
for available function templates, you can force the compiler to
choose that function instead of the function template. To do this,
you'll declare a separate nontemplate function that's
compatible with the existing function template.
If you read the three steps above, you may notice that the first step looks for a matching function declaration. In fact, if you provide a matching nontemplate function declaration only (without defining the function), the compiler will prefer that form of the template function to other possible forms, and it will create that version of the function template whenever possible.
For example, if you try to use the equal( ) function
template (shown earlier) with an int value and a char
value, the compiler will display the error message
Could not find a match for 'equal(int, char)'
This error appears even though the compiler "could" create the equal(int&, int&) version of the template and then perform a trivial conversion of a char variable to an int variable. However, the call is ambiguous because the compiler could also create an equal(char&, char&) version of the template. Neither call is preferable according to the guidelines above nor is it preferable according to the standard rules for conversions.
However, if you declare an equivalent version of the function
by writing
int equal(const int&, const int&);
the compiler will perform a syntax check using that declaration,
but it will use the function template to generate the actual function
body. Since the equal(int&, int&) declaration
exists, the compiler considers that function a match for the
equal(int, char) function call and converts the values.
As we've just mentioned, providing a specific declaration of a function template can help the compiler choose a specific version of a function template. However, if you don't provide a function body for that declaration, the compiler will use the existing template body to manufacture the function for you.
If you need to provide a special implementation of a function
template for a given type, you'll need to provide a declaration
and a function body for that type. If you then call the
function, the compiler will use the explicit version of the function
and will ignore the template function entirely.
To see how you can specialize function templates, let's build a simple EasyWin application. First, launch the Borland C++ Integrated Development Environment (IDE).
When the IDE's main window appears, choose New from the File menu. When the new editing window appears, right-click on the window and choose TargetExpert. In the Target Expert dialog box, select EasyWin [.exe] from the Target Type list box and click OK. Next, enter the source code from Listing A.
Listing A: TMPL_TST.CPP
#include <iostream.h> #include <string.h> template <class T> int equal(const T& a, const T& b) { return a == b; } // 1st attempt at Special declaration // int equal(const int, const int); // 2nd attempt at Special declaration // int equal(const int&, const int&); // Overloaded function - Specialized for char * // int equal(const char* a, const char* b) // { return !strcmp(a, b); } int main( ) { int intVal = 1; char charVal = 1; cout << "equal( ) - "; cout << equal(intVal , intVal) << endl; return 0; }
When you finish entering the code, choose Save As... from the File menu. In the Save File As dialog box, enter TMPL_TST.CPP and click OK.
To build and run this application, choose Run from the Debug menu.
When the program runs, you'll see the output
equal( ) - 1
appear in the EasyWin output screen. Close the EasyWin output screen.
In the editing window for the file TMPL_TST.CPP, change the line
that compares the variable i to itselfcout
<< equal(intVal , intVal) << endl;to
cout << equal(intVal , charVal) << endl;
This attempts to compare an int value with a char value by using the equal( ) function template.
Choose Run from the Debug menu again. This time, the compiler
will stop and report an error. Click OK in the Compile Status
dialog box, and you'll see the message
Could not find a match for 'equal(int, char)' in function main( )
Since the compiler can't decide which conversion (int - char, or char - int) to use, it doesn't create either one. When it can't find a matching function for the equal( ) function, the compiler refuses to choose between the two template versions.
Next, remove the comment tokens (//) from line 9 of the file TMPL_TST.CPP, which contains a first attempt at a specialized declaration. Choose Run from the Debug menu to rebuild the application.
This time, the compiler will build the source file, but you'll
see an error message from the Linker that reads
Undefined symbol equal(const int,const ...) in module...
This message appears because the function signature isn't compatible with the signature of the template (the template uses the reference parameters const T&). Remember, to overload a function, you must create another function that has the exact name and argument list as the first function.
To fix this problem, reinsert the comment tokens at the beginning of line 9 and remove them from line 12. This enables a function declaration that uses reference parameters, just like the function template.
Choose Run from the Debug menu to rebuild the application. When
the program runs, you'll see the same output as before
equal( ) - 1
(since the actual values of intVal and charVal are the same). Close the EasyWin output screen to return to the IDE.
Return to the editing window for this application and remove the comment tokens from lines 15 and 16. This enables an overloaded version of the equal( ) function that will correctly compare C-style strings.
Then, change the code that appears on lines 20 through 24 to:
// int intVal = 1; // char charVal = 1; cout << "equal( ) - "; cout << equal("bcdj", "bcdj") << endl;
The last line now calls the overloaded equal( ) function with two identical strings.
When you choose Run from the Debug menu again, you'll see
the same output as before
equal( ) - 1
Close the EasyWin output window to return to the IDE; then, choose
Exit from the File menu.
The technique we've shown here is useful for function templates,
but it also works for member functions of class templates. This
allows you to special-case one member function or algorithm in
the class without having to rewrite the rest of the class for
a particular type.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.