Function Templates


Function Templates

If we wanted a cube(...) function, this is how we would implement it using overloaded functions:

Overloaded functionsClient calls

 

 
int cube(int val)
{
 return val * val * val;
}

long cube(long val)
{
 return val * val * val;
}

float cube(float val)
{
 return val * val * val;
}

double cube(double val)
{
 return val * val * val;
}
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

This is our new cube function using a template:
template <typename T> T cube(T value)
{
  return value * value * value;
}
Often, the function template is written with the template and typename keywords on a separate line above:
template <typename T> 
T cube(T value)
{
  return value * value * value;
}
Other notes: Note that often the compiler can deduce the types of the arguments (and hence, the type of T) from the parameters. This is very different from class templates where the user must specify the type of class to instantiate. We'll see that sometimes the compile can't deduce all of the types for a function template.


User-Defined Types in Template Functions

Templates only provide some of the necessary functionality.

Suppose we wanted to cube a StopWatch object:

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; // ???
Will this compile? If so, what will it print out? To understand what's going on, look at what the compiler is generating:
StopWatch cube(StopWatch value)
{
  return value * value * value;
}
Will this cube function compile? Why or why not? The answer is: it depends.

StopWatch-1.h
StopWatch-1.cpp

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:
template<typename T> 
int IndexOf(const T *array, const T& value, int size)
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:

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

Multiple Template Parameters

Suppose we want a generic Max function:
template <typename T>
T Max(T a, T b)
{
  return a > b ? a : b;
}
we try to use it like this:
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)'
We need to specify the type of both parameters in order to mix types:

template <typename T1, typename T2>
T1 Max(T1 a, T2 b)
{
  return a > b ? a : b;
}
This leads to:
double d1 = Max(4.5, 3); // d2 = 4.5 
double d2 = Max(3, 4.5); // d1 = 4.0 ???
Finally, add a third type:
template <typename T1, typename T2, typename T3>
T1 Max(T2 a, T3 b)
{
  return a > b ? a : b;
}
This leads to:
double d1 = Max(3, 4.5); // error: can't deduce template argument for 'T1' 
Various solutions:
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
Suppose I made this subtle change:
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?


Overloaded Template Functions

Suppose we have this "universal" swap function:
template <typename T>
void Swap(T &a, T &b)
{
  T temp = a;
  a = b;
  b = temp;
}
and a trivial use case:
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
What is the result of this "Swap"?

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++:

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
Borland:
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])
We need a function that can deal with arrays, so we overload the template:

  // overloaded template function
template <typename T>
void Swap(T *a, T *b, int size)
{
  T temp;
  for (int i = 0; i < size; i++)
  {
    temp = a[i];
    a[i] = b[i];
    b[i] = temp;
  }
}
  // original template function
template <typename T>
void Swap(T &a, T &b)
{
  T temp = a;
  a = b;
  b = temp;
}
Use:

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

Explicit Template Specialization

Up until now, all of our templated functions have been generated from the template. (Which was the whole point.)

What if we (the programmer) implement a function that matches an instantiated function?

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:
  1. Regular functions
  2. Explicit specializations
  3. Template generated
You can force the compiler to choose a particular function by explicitly stating which function to use.

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:
5
4.5
a
apple
The fix:
// 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;
}

Explicit Template Instantiation

Up until now, all of our templated functions have been generated "on-demand", meaning that the code for a templated function wasn't actually generated until we needed (called) it. (Which, again, was the whole point.)

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:
driver.cpp: undefined reference to `double cube(double)'
driver.cpp: undefined reference to `float cube(float)'
driver.cpp: undefined reference to `char cube(char)'
We need to declare these "missing" functions before main:

// 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>
Note that the syntax is very similar to explicit specialization:
template <> double cube(double);  // Explicit specialization
template double cube(double);     // Explicit instantiation

Example:

Given these files: Cube files The default compilation mode for Borland (and all probably all other compilers) is to instantiate and compile templates in each file where they are needed. So, using Borland's compiler, these commands:
bcc32 -c cubemain.cpp 
bcc32 -c filecube1.cpp 
bcc32 -c filecube2.cpp 
bcc32 -c cube.cpp
generate these object files (partial directory listing):
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):
bcc32 -c -Jgx cubemain.cpp 
bcc32 -c -Jgx filecube1.cpp 
bcc32 -c -Jgx filecube2.cpp 
bcc32 -c cube.cpp
generate these object files (partial directory listing):
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.


Resolving Function Calls

With overloading, templates, and specializations, there can be several functions that might resolve to a specific call.
  1. Find all of the regular functions that have the same name as the function and template functions that can be instantiated from the parameters. (Candidate functions)
  2. Now consider the parameters of the functions and keep the ones that are applicable. (Viable functions)
  3. Consider the type conversions, if any: No conversion (exact match/trivial), promotions, built-in, user-defined. Rank them from more specialized to less specialized. (Stronger match to weaker match).
    1. If there is only one function, choose it.
    2. If there are more than one equally viable functions, the call is ambiguous. Remove the template instantiations from the set.
  4. Consider only ordinary functions (non-template) from the viable set.
    1. If there is only one function, choose it.
    2. If there are more than one equally viable functions, it's ambiguous and is an error.
Example:

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;
}
Output:
Min int (template): (0, 5)
Min double (regular): (1.2, 3.14)
Min double (regular): (0, 2.5)
Suppose we add an int regular function so we have this:

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;
}
How does this affect the last call in main?
  Min(0, f);   // Which function will be called?
Following the rules: These are the error messages from 3 different compilers. Note that even without the regular int function, MSVC++ 6.0 won't compile.


MSVC++ 7.1: cl driver2.cpp /EHs

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)'
GNU g++ 3.2: g++ 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)
Borland C++ 5.6.4: bcc32 driver2.cpp

driver2.cpp:
Error E2015 driver2.cpp 40: Ambiguity between 'Min(double,double)' and 'Min(int,int)' in function main()


What happens if we define a float version instead of the int version so we have these now?

double Min(double a, double b);
float Min(float a, float b);
How does this affect the last call in main?
  Min(0, f);   // Which function will be called?

Self-check

Template Specialization and Overloading Additional examples of function template overloading.
Why Not Specialize Function Templates? More advanced information about function templates.


Compiling Templates

How does the compiler and linker deal with the following code?

  // In main.cpp
#include <iostream> 
int min(int, int); // prototype
int main(void) 
{ 
  std::cout << min(5, 6) << std::endl;
  return 0;
}
  // In min.cpp
int min(int a, int b)
{
  return a < b ? a : b; 
}

How about this code:

  // In main.cpp
#include <iostream> 

  // declaration only
template <class T> 
T const& min(T const&, T const&); 

int main(void) 
{ 
  std::cout << min(5, 6) << std::endl;
  return 0;
}
  // Definition in min.cpp
template <class T> 
T const& min(T const &a, T const &b) 
{ 
  return a < b ? a : b; 
}

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:

  // In main.cpp
#include <iostream> 

  // declaration only
export template <class T> 
const T & min(const T &, const T &); 

int main(void) 
{ 
  std::cout << min(5, 6) << std::endl;
  return 0;
}
  // Definition in min.cpp
export template <class T> 
const T & min(const T &a, const T &b) 
{ 
  return a < b ? a : b; 
}
If it were only that easy...

Most compilers do not implement export, so this solution will not work (yet).

One solution is to explicitly instatiate the function:

  // In main.cpp
#include <iostream> 

  // declaration only
template <class T> 
const T & min(const T &, const T &); 

int main(void) 
{ 
  std::cout << min(5, 6) << std::endl;
  return 0;
}
  // Definition in min.cpp
template <class T> 
const T & min(const T &a, const T &b) 
{ 
  return a < b ? a : b; 
}

  // explicit instantiation
template const int& min(int const &a, int const &b); 
An alternative solution is to put the definition in the header file and include it in all files that need the template:

  // In main.cpp
#include <iostream> 
#include "min.h"

int main(void) 
{ 
  std::cout << min(5, 6) << std::endl;
  return 0;
}
  // Definition in min.h
#ifndef _MIN_H
#define _MIN_H

template <class T> 
const T & min(const T &a, const T &b) 
{ 
  return a < b ? a : b; 
}
#endif //_MIN_H

More information on template instantiation and how to control it.
A good overview of the export keyword is here: Comeau C++ Export Overview

An article describing the difficulty in implementing exported templates.