Function templates

Function templates

Templates can also be used to define functions which work on different types of objects. Consider the following function to swap two values of type double:

void mySwap(double & a, double & b)
{
  double temp = a;
  a = b;
  b = temp;
}

If we want to swap two integers, we need to overload function mySwap with another definition.

void mySwap(int & a, int & b)
{
  double temp = a;
  a = b;
  b = temp;
}

We need to do the same if we want to swap two variables of type float, type bool, type string, etc. Large Fortran and C programs and libraries are often cluttered with numerous versions of the same function for different parameter types.

Function templates removes the need for multiple definitions of the same function with different parameter types. The function template for mySwap is:

template <typename T>
void mySwap(T & a, T & b)
{
  T temp = a;
  a = b;
  b = temp;
}
...
  int i(5), j(8);
  float x(3.3), y(4.4);
  double u(5.5), v(6.6);
  bool b1(true), b2(false);

  mySwap(i,j);
  mySwap(x,y);
  mySwap(u,v);
  mySwap(b1,b2);
...

The template mySwap is so useful that it is defined as template function "swap" in include file "algorithm" in the Standard Template Library (STL). To make your code easier for others (and you) to read, always use the function "swap" from the Standard Template Library. Do not define your own function "swap" or "mySwap".

#include <algorithm>    // File containing function template swap.
using namespace std;    // Function template swap is in the std namespace.
...
  int i(5), j(8);
  float x(3.3), y(4.4);
  double u(5.5), v(6.6);
  bool b1(true), b2(false);

  swap(i,j);
  swap(x,y);
  swap(u,v);
  swap(b1,b2);
...

Example of computing the inner product

Assume you want to compute the inner product of two arrays. The following code computes the inner product of arrays of type double:

void inner_product(const double u[], const double v[], 
                   const int n, double & result)
{
  result = 0;
  for (int i = 0; i < n; i++)
    { result = result + u[i]*v[i]; }
}

If you want a function to compute the inner product of two arrays of type int, you need to create a copy of the function with arguments of type int. You need another copy for the inner product of arrays of type float and of type short and of type long, etc. Fortran libraries contain multiple copies of functions such as inner_product with slightly different names (operation overloading is not supported in Fortran) and different parameter types.

In C++ you can create one template function which computes the inner product for all such types:

template <typename T>
void inner_product(const T u[], const T v[], const int n, T & result)
{
  result = 0;
  for (int i = 0; i < n; i++)
    { result = result + u[i]*v[i]; }
}
...
  double x[LENGTH], y[LENGTH], prod_x_y;
  float u[LENGTH], v[LENGTH], prod_u_v;
  int a[LENGTH], b[LENGTH], prod_a_b;
...
  inner_product(x, y, LENGTH, prod_x_y);
  inner_product(u, v, LENGTH, prod_u_v);
  inner_product(x, y, LENGTH, prod_a_b);
...

However, the function above cannot be used to compute the inner product of two arrays of different types. For instance, the following example generates a compiler error:

 6. template <typename T>
 7. void inner_product(const T u[], const T v[], const int n, T & result)
 8. {
 9.   result = 0;
10.  for (int i = 0; i < n; i++)
11.    { result = result + u[i]*v[i]; }
12. }
...
17.   float x[LENGTH], result;
18.   int a[LENGTH];
...
25.   inner_product(x, a, LENGTH, result);
...

The compiler error is:

> g++ inner_product_error1.cpp
inner_product_error1.cpp: In function 'int main()':
inner_product_error1.cpp:25: error: no matching function for call to 
        'inner_product(float [5], int [5], const int&, float&)'

The problem is that the template specification requires both u and v to be arrays of the same type and for result to have the same type as well. We can modify template to compute the inner product of two arrays with different types as follows:

template <typename T1, typename T2, typename T3>
void inner_product(const T1 u[], const T2 v[], const int n, T3 & result)
{
  result = 0;
  for (int i = 0; i < n; i++) {
    result = result + u[i]*v[i]; 
  }
}
...
   float x[LENGTH];
   int a[LENGTH];
   double result;
...
   inner_product(x, a, LENGTH, result);

Note that the result also may have a different type. This creates the potential for numerical error if result has type int while the arrays have type float or double.

Examples of function template translate

Function templates can be used to perform the same operation on different objects. For instance, the following function template adds (dx,dy) to an object's x and y locations.

template <typename T1, typename T2>
void translate(const T1 dx, const T1 dy,
               T2 & object)
{
  object.x += dx;
  object.y += dy;
}

We can apply translate to any class which has members x and y:

...
class Circle
{
public:
  double x, y;
  int radius;
};

class Rectangle 
{
public:
  int x, y;
  int width, height;
};

class Label
{
public:
  float x, y;
  string name;
};


int main()
{
  Circle c;
  Rectangle r;
  Label l;
...
  translate(0.1, 0.2, c);
  translate(1, 2, r);
  translate(0.1, 0.2, l);
...

Note that classes Circle, Rectangle and Label have different types for x and y and have additional members other than x and y. All that is necessary is that the class have a member x and a member y.

For the simple example above, a better solution would be to create a class Point with members x and y and a member function translate. Derive classes Circle, Rectangle and Label from class Point. However, for more complicated examples where the different functions on different elements of each class, using derived classes can get very complicated and require multiple inheritance. Using template functions may then be the simplest solution.

Consider again procedure translate:

template <typename T1, typename T2>
void translate(const T1 dx, const T1 dy,
               T2 & object)
{
  object.x += dx;
  object.y += dy;
}

Note that dx and dy have the same types T1. This can cause a compiler error if dx is an integer and dy is a float of vice versa.

37.  translate(1, 0.2, c);
48.  translate(0.1, 0, l);

Compiling the above code generates the following compiler errors:

>  g++ translate_error1.cpp
translate_error1.cpp: In function 'int main()':
translate_error1.cpp:37: error: no matching function 
    for call to 'translate(int, double, Circle&)'
translate_error1.cpp:38: error: no matching function 
    for call to 'translate(double, int, Label&)'

One solution is to use only floats. The following code compiles without errors:

translate(1.0, 0.2, c);
translate(0.1, 0.0, l);

Another solution is to use separate types for dx and dy. In the following code, parameter dx has template type T1 while parameter dy has template type T2. The code compiles without errors:

template <typename T1, typename T2, typename T3>
void translate(const T1 dx, const T2 dy,
               T3 & object)
{
  object.x += dx;
  object.y += dy;
}
...
  translate(1, 0.2, c);
  translate(0.1, 0, l);
...

Template specialization

Consider the following LineSegment class for storing a line segment:

class LineSegment
{
public:
  float x1, y1;  // Coordinates of endpoint 1
  float x2, y2;  // Coordinates of endpoint 2
};

The endpoints of the line segment are (x1,y1) and (x2,y2). We would like the template translate to apply to LineSegment. However, LineSegment has two endpoints, not one, and both endpoints must be modified.

We define a "specialization" of template translate which applies to LineSegment:

template <typename T1, typename T2>
void translate(const T1 dx, const T2 dy,
               LineSegment & object)
{
  object.x1 += dx;
  object.y1 += dy;
  object.x2 += dx;
  object.y2 += dy;
}

Note that this specialization has only two template types and that object has the type LineSegment. This specialization overides the generic translate where object has the template type T3. The compiler chooses this specialized template whenever object has the type LineSegment.

The following code sets the line segment to ((0,10),(5,15)) and then translate the line segment by (0.1,0.2) to the line segment ((0.1,10.2),(5.1,15.2)).

  LineSegment seg;

  seg.x1 = 0;
  seg.y1 = 10;
  seg.x2 = 5;
  seg.y2 = 15;

  translate(0.1, 0.2, seg);

Template function errors

One common error in writing template functions is to have a template parameter type which does not appear in the function argument list. For instance, in the following example there are three template parameter types, T1, T2, T3, but only T1 and T3 are referenced in the function header.

 7. // ERROR: Template parameter type T2 is not in function parameter list.
 8. template <typename T1, typename T2, typename T3>
 9. void translate(const T1 dx, const T1 dy,
10.                T3 & object)
11. {
12.   object.x += dx;
13.   object.y += dy;
14. }
...
32.   Circle c;
33.   Label l;
...
38.  translate(1, 2, c);
39.  translate(3, 4, l);
...

This definition of the template function is not illegal. (There are ways of calling this function by explicitly specifying T1, T2 and T3.) However, in the call to translate in lines 38 and 39, the compiler does not know the type of T2 and generates the following error message:

> g++ translate_error2.cpp
translate_error2.cpp: In function 'int main()':
translate_error2.cpp:38 error: no matching function 
    for call to 'translate(int, int, Circle&)'
translate_error2.cpp:39 error: no matching function 
    for call to 'translate(int, int, Label&)'

Note that the error is in the function header in lines 7 and 8, but the error message refers to lines 38 and 39.

The following code contains a similar error in the specialization of translate for the object LineSegment.

...
28. template <typename T1, typename T2, typename T3>
29. void translate(const T1 dx, const T2 dy,
30.                T3 & object)
31. {
32.   object.x += dx;
33.   object.y += dy;
34. }
35. 
36. // ERROR: Template parameter type T3 is not in function parameter list.
37. template <typename T1, typename T2, typename T3>
38. void translate(const T1 dx, const T2 dy,
39.                LineSegment & object)
40. {
41.   object.x1 += dx;
42.   object.y1 += dy;
43.   object.x2 += dx;
44.   object.y2 += dy;
45. }
...
52.   LineSegment seg;
...
64.   translate(1, 2, seg);

In the call to translate in line 64, the call to translate does not match the specificatin in lines 37-39 because type T3 is not defined. The compiler matches the call to translate in line 64 with the template definition in lines 28-34. However, type LineSegment does not have members x and y and the compiler generates the following error messages:

g++ translate_error3.cpp
translate_error3.cpp: In function 'void translate(T1, T2, T3&) 
    [with T1 = int, T2 = int, T3 = LineSegment]':
translate_error3.cpp:64:   instantiated from here
translate_error3.cpp:32: error: 'class LineSegment' has no member named 'x'
translate_error3.cpp:33: error: 'class LineSegment' has no member named 'y'

Note that the programming error was in line 37 but the error messages refer to lines 64, 32 and 33. Detecting such errors can be extremely difficult.

CSE 2122 material is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. Source code for this website available at GitHub.