| Overloaded functions | Client calls | |
|---|---|---|
|
int i = cube(2); // cube(int), i is 8 long l = cube(100L); // cube(long), l is 1000000L float f = cube(2.5f); // cube(float), f is 15.625f double d = cube(2.34e25); // cube(double), d is 1.2812904e+76 |
template <typename T> T cube(T value)
{
return value * value * value;
}
template <typename T>
T cube(T value)
{
return value * value * value;
}
Other notes:
then code similar to this is generated for us:int i = cube(2);
int cube(int value)
{
return value * value * value;
}
then code similar to this is generated for us:int i = cube(2); long l = cube(100L); float f = cube(2.5f); double d = cube(2.34e25);
|
|
Suppose we wanted to cube a StopWatch object:
Will this compile? If so, what will it print out? To understand what's going on, look at what the compiler is generating:StopWatch sw1(4); // Create a StopWatch set to 4 seconds StopWatch sw2; // Create a StopWatch set to 0 seconds sw2 = cube(sw1); // Cube the first StopWatch and assign to second cout << sw2 << endl; // ???
StopWatch cube(StopWatch value)
{
return value * value * value;
}
Will this cube function compile? Why or why not? The answer is: it depends.
If there is no overloaded operator*, VC++ generates this error message in the template function:
template <typename T>
T cube(T value)
{
return value * value * value; // error C2678: binary '*' : no operator defined ...
}
We need to ensure that there is an overloaded * operator that takes a StopWatch on the left and right side of the operator:
StopWatch StopWatch::operator*(const StopWatch &sw) const
{
return StopWatch(m_seconds * sw.m_seconds);
}
Now, the following will compile:
StopWatch sw1(4); // Create a StopWatch set to 4 seconds StopWatch sw2; // Create a StopWatch set to 0 seconds sw2 = cube(sw1); // Cube the first StopWatch and assign to second cout << sw2 << endl; // Displays 00:01:04 (4 * 4 * 4 = 64 seconds)
Examples:
As you look at these, ask yourself what methods need to be present in a user-defined class for these template functions to generate valid code.
Template:
template<typename T>
void Display(const T *array, int size)
{
// Display each element in the array
// on one line separated by commas
for (int i = 0; i < size; i++)
{
if (i > 0)
std::cout << ", ";
std::cout << array[i];
}
std::cout << std::endl;
}
Template:
template<typename T>
int IndexOf(const T *array, T value, int size)
{
for (int i = 0; i < size; i++) // for each element in the array
if (array[i] == value) // if the element matches the supplied value
return i; // return the index (we've found it).
return -1; // return -1 to indicate we didn't find it.
}
Use:
int a[] = {4, 6, 3, 8, 4, 5, 7};
int size = sizeof(a)/sizeof(*a);
int i1 = IndexOf(a, 8, size); // 3
int i2 = IndexOf(a, 2, size); // -1
StopWatch sw1[] = {StopWatch(60), StopWatch(90), StopWatch(0)};
int i3 = IndexOf(sw1, StopWatch(120), 3); // -1
int i4 = IndexOf(sw1, StopWatch(0), 3); // 2
StopWatch s1(60), s2(90), s3(0);
StopWatch *sw2[] = {&s1, &s2, &s3};
StopWatch s4(60);
int i5 = IndexOf(sw2, &s4, 3); // ???
i5 = IndexOf(sw2, &s2, 3); // ???
Of course, we'll need to implement operator== for the StopWatch class in
order for the above to compile. Also, a better way to implement the function:
With integers and other built-in types, there is no big difference. However, with user-defined types, there can be a very big difference. Keep this in mind when implementing templated functions.template<typename T> int IndexOf(const T *array, const T& value, int size)
Template:
template<typename T>
T LargestValue(const T *array, int size)
{
T largest = array[0]; // assume the first element is the largest
for (int i = 1; i < size; i++) // traverse the array looking for larger ones
if (array[i] > largest) // if this element is larger than the largest
largest = array[i]; // make it the new largest
return largest; // return the largest element found
}
Use:
int a[] = {4, 6, 3, 8, 4, 5, 7};
StopWatch sw1[] = {StopWatch(60), StopWatch(90), StopWatch(0)};
int i = LargestValue(a, 7); // 8
StopWatch sw = LargestValue(sw1, 3); // 00:01:30
A possibly better implementation:
template<typename T>
const T& LargestValue(const T *array, int size)
{
int index = 0; // assume the first element is the largest
for (int i = 1; i < size; i++) // traverse the array looking for larger ones
if (array[i] > array[index]) // if this element is larger than the largest
index = i; // make it the new largest
return array[index]; // return the largest element found
}
If we change the first implementation above to this:
template<typename T>
T LargestValue(const T *array, int size)
{
T largest;
largest = array[0]; // assume the first element is the largest
// . . .
}
How will this affect things?
We'll also need to implement operator> for the StopWatch class in order for the above to compile.
Template:
template<typename T>
int Replace(T *array, const T& oldvalue, const T& newvalue, int size)
{
int replacements = 0; // assume no replacements
for (int i = 0; i < size; i++) // for each element in the array
{
if (array[i] == oldvalue) // if it matches the 'oldvalue'
{
array[i] = newvalue; // change it to the 'newvalue'
replacements++; // inc counter
}
}
return replacements; // return number of replacements
}
Use:
int a[] = {4, 6, 3, 8, 4, 5, 7};
StopWatch sw1[] = {StopWatch(60), StopWatch(90), StopWatch(0)};
Replace(a, 6, 2, 7); // Before: 4, 6, 3, 8, 4, 5, 7
Replace(sw1, StopWatch(60), StopWatch(120), 3); // Before: 00:01:00, 00:01:30, 00:00:00
Display(a, 7); // 4, 2, 3, 8, 4, 5, 7
Display(sw1, 3); // 00:02:00 , 00:01:30, 00:00:00
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
we try to use it like this:
We need to specify the type of both parameters in order to mix types:int i = Max(25, 10); // i = 25 double d = Max(2.5, 3.1); // d = 3.1 double e = Max(2.5, 4); // error: no function `Max(double, int)'
template <typename T1, typename T2>
T1 Max(T1 a, T2 b)
{
return a > b ? a : b;
}
This leads to:
Finally, add a third type:double d1 = Max(4.5, 3); // d2 = 4.5 double d2 = Max(3, 4.5); // d1 = 4.0 ???
template <typename T1, typename T2, typename T3>
T1 Max(T2 a, T3 b)
{
return a > b ? a : b;
}
This leads to:
Various solutions:double d1 = Max(3, 4.5); // error: can't deduce template argument for 'T1'
Suppose I made this subtle change:double d2 = Max<double, int, double>(3, 4.5); // OK, explicit double d3 = Max<double, double, int>(4.5, 3); // OK, explicit double d4 = Max<double>(3, 4.5); // Ok, explicit, compiler deduces others double d5 = Max<double, int, int>(4.5, 3); // OK, explicit, but truncating double d6 = Max<int, double, int>(4.5, 3); // OK, explicit, but truncating
template <typename T1, typename T2, typename T3>
T3 Max(T1 a, T2 b)
{
return a > b ? a : b;
}
How does that affect the code above? Other calls to Max?
template <typename T>
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
and a trivial use case:
What is the result of this "Swap"?int i = 10, j = 20; double d = 3.14, e = 2.71; Swap(i, j); // i=20, j=10 Swap(d, e); // d=2.71, e=3.14
int a[] = {1, 3, 5, 7, 9, 11};
int b[] = {2, 4, 6, 8, 10, 12};
Swap(a, b); // ???
To get an idea of what's going on, look at what the instantiated function might look like:
void Swap(int a[6], int b[6])
{
int temp[6] = a; // 146. initializing an array with array
a = b; // 147. assigning an array
b = temp; // 148. assigning an array
}
This produces errors because T instantiates to int[6]:
g++:
Borland:driver.cpp: In function `void Swap(T&, T&) [with T = int[6]]': driver.cpp:266: instantiated from here driver.cpp:146: error: invalid initializer driver.cpp:147: error: ISO C++ forbids assignment of arrays driver.cpp:148: error: ISO C++ forbids assignment of arrays
We need a function that can deal with arrays, so we overload the template:Error E2034 driver.cpp 146: Cannot convert 'int *' to 'int[6]' in function void Swap(int ( &)[6],int ( &)[6]) Error E2277 driver.cpp 147: Lvalue required in function void Swap (int ( &)[6],int ( &)[6]) Error E2277 driver.cpp 148: Lvalue required in function void Swap (int ( &)[6],int ( &)[6])
|
|
int a[] = {1, 3, 5, 7, 9, 11};
int b[] = {2, 4, 6, 8, 10, 12};
int size = sizeof(a)/sizeof(*a);
Swap(a, b, size); // calls Swap(int *, int *, int) [with T = int]
Display(a, size); // 2, 4, 6, 8, 10, 12
Display(b, size); // 1, 3, 5, 7, 9, 11
What if we (the programmer) implement a function that matches an instantiated function?
template <>
Example:
#include <iostream>
#include <typeinfo>
using namespace std;
// template function
template <typename T> T cube(T value)
{
cout << "Cubing a " << typeid(T).name() << " (template): " << value << endl;
return value * value * value;
}
// regular function (non-template)
int cube(int value)
{
cout << "Cubing an int (regular): " << value << endl;
return value * value * value;
}
// explicit specialization cube<int>
template <> int cube(int value)
{
cout << "Cubing an int (specialization): " << value << endl;
return value * value * value;
}
int main(void)
{
// MSVC 6.0 GNU/BOR/MS7.1
cube(5); // regular regular
cube<double>(10L); // template template
cube(2.5F); // template template
cube<int>(2.5); // regular special
cube<char>(5); // template template
cube('A') // template template
return 0;
}
Output
Cubing an int (regular): 5
Cubing a double (template): 10
Cubing a float (template): 2.5
Cubing an int (specialization): 2
Cubing a char (template): ???
Cubing a char (template): A
When the compiler must choose a function, the order of choice is:
Also note that you cannot create an explicit specialization for a function after an implicit instantiation for that same function has been generated. For example, if we had this code before the explicit specialization for the cube function taking an int:
void foo(void)
{
// implicitly instantiates cube for integers
int i = cube<int>(25);
}
The explicit specialization following this will generate a compiler error along these lines:
specialization of T cube(T) [with T = int] after instantiation (GNU) Template instance 'int cube(int)' is already instantiated (Borland) explicit specialization; 'T cube (T)' has already been instantiated (MSC 7.1)
Another example. Given the templated Max function below:
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
What is printed here?
int main(void)
{
cout << Max(5, 2) << endl;
cout << Max(4.5, 3.14) << endl;
cout << Max('A', 'a') << endl;
cout << Max("banana", "apple") << endl;
return 0;
}
Output:
The fix:5 4.5 a apple
// Explicit specialization for const char *
// to sort lexicographically
template <> const char* Max(const char *a, const char *b)
{
return strcmp(a, b) >= 0 ? a : b;
}
We can instruct the compiler to instantiate a template at any time during the compilation process.
The easiest way to show this is to disable the implicit template instantiations using the GNU compiler with the command line switch -fno-implicit-templates.
int main(void)
{
cube(5); // Ok, uses regular function
cube<double>(10L); // Link error: Undefined reference
cube(2.5F); // Link error: Undefined reference
cube<int>(2.5); // Ok, uses specialization
cube<char>(5); // Link error: Undefined reference
return 0;
}
we'll get linker errors like this:
We need to declare these "missing" functions before main:driver.cpp: undefined reference to `double cube(double)' driver.cpp: undefined reference to `float cube (float)' driver.cpp: undefined reference to `char cube (char)'
Note that the syntax is very similar to explicit specialization:// explicit instantiations, declaration only template double cube(double); // Deduces cube<double> template float cube(float); // Deduces cube<float> template char cube(char); // Deduces cube<char>
template <> double cube(double); // Explicit specialization template double cube(double); // Explicit instantiation
generate these object files (partial directory listing):bcc32 -c cubemain.cpp bcc32 -c filecube1.cpp bcc32 -c filecube2.cpp bcc32 -c cube.cpp
03/02/2005 12:33p 17,078 cube.obj
03/02/2005 12:33p 17,307 cubemain.obj
03/02/2005 12:33p 17,281 filecube1.obj
03/02/2005 12:33p 17,281 filecube2.obj
4 File(s) 68,947 bytes
Modify the command to include -Jgx (Generate external references for templates):
generate these object files (partial directory listing):bcc32 -c -Jgx cubemain.cpp bcc32 -c -Jgx filecube1.cpp bcc32 -c -Jgx filecube2.cpp bcc32 -c cube.cpp
03/02/2005 12:33p 17,078 cube.obj
03/02/2005 12:33p 9,591 cubemain.obj
03/02/2005 12:33p 9,565 filecube1.obj
03/02/2005 12:33p 9,565 filecube2.obj
4 File(s) 45,799 bytes
Note that the size of the executable doesn't change. Why not?
To link the program into a file called cube.exe, you would do this:
bcc32 -ecube.exe cubemain.obj filecube1.obj filecube2.obj cube.obj
More information on template instantiation and how to control it.
MSVC++ 7.1: cl driver2.cpp /EHs
What happens if we define a float version instead of the int version
so we have these now?
Template Specialization and Overloading Additional examples of function template overloading.
Resolving Function Calls
With overloading, templates, and specializations, there can be several functions that
might resolve to a specific call.
Example:
Output:
template <typename T>
T Min(T a, T b)
{
cout << "Min " << typeid(T).name() << " (template): (" << a << ", " << b << ")" << endl;
return a < b ? a : b;
}
double Min(double a, double b)
{
cout << "Min double (regular): (" << a << ", " << b << ")" << endl;
return a < b ? a : b;
}
int main(void)
{
int i = 5;
double d = 3.14;
float f = 2.5F;
Min(0, i); // Both int, template instantiation
Min(1.2, d); // Both double, ambiguous, template is removed from viable set
Min(0, f); // Different types, can't use one T in template, calls regular function
return 0;
}
Suppose we add an int regular function so we have this:
Min int (template): (0, 5)
Min double (regular): (1.2, 3.14)
Min double (regular): (0, 2.5)
How does this affect the last call in main?
Template Regular (double) Regular (int)
T Min(T a, T b)
{
return a < b ? a : b;
}
double Min(double a, double b)
{
return a < b ? a : b;
}
int Min(int a, int b)
{
return a < b ? a : b;
}
Following the rules:
Min(0, f); // Which function will be called?
These are the error messages from 3 different compilers. Note that even without the regular
int function, MSVC++ 6.0 won't compile.
GNU g++ 3.2: g++ driver2.cpp
driver2.cpp(40) : error C2666: 'Min' : 2 overloads have similar conversions
driver2.cpp(26): could be 'int Min(int,int)'
driver2.cpp(19): or 'double Min(double,double)'
while trying to match the argument list '(int, float)'
Borland C++ 5.6.4: bcc32 driver2.cpp
driver2.cpp: In function `int main()':
driver2.cpp:40: call of overloaded `Min(int, float&)' is ambiguous
driver2.cpp:20: candidates are: double Min(double, double)
driver2.cpp:27: int Min(int, int)
driver2.cpp:
Error E2015 driver2.cpp 40: Ambiguity between 'Min(double,double)' and 'Min(int,int)' in function main()
How does this affect the last call in main?
double Min(double a, double b);
float Min(float a, float b);
Min(0, f); // Which function will be called?
Why Not Specialize Function Templates? More advanced information about function templates.
Compiling Templates
How does the compiler and linker deal with the following code?
|
|
How about this code:
|
|
The ideal solution is to export the template defintions. According to Greg Comeau, "An exported template is special because its definition does not need to be present in a translation unit that uses that template."
Using the export keyword:
|
|
Most compilers do not implement export, so this solution will not work (yet).
One solution is to explicitly instatiate the function:
|
|
|
|
An article describing the difficulty in implementing exported templates.