Function templates

Multiple functions may be nearly identical, differing only in their data types, as below.

Function Overloading Example: Functions may have identical behavior, differing only in data types.


// Find the minimum of three numbers
int TripleMin(int item1, int item2, int item3) {
    int minVal;

    minVal = item1;

    if(item2 < minVal) {
        minVal = item2;
    }
    if(item3 < minVal) {
        minVal = item3;
    }
    return minVal;
}

// Find the minimum of three "chars"
char TripleMin(char item1, char item2, char item3) {
    char minVal;

    minVal = item1;

    if(item2 < minVal) {
        minVal = item2;
    }
    if(item3 < minVal) {
        minVal = item3;
    }
    return minVal;
}
    

Writing and maintaining redundant functions that only differ by data type can be time consuming and error prone. C++ supports a better approach.

A function template is a function having a special type parameter that may be used in place of types in the function.

A function template enables a function to handle various data types.


#include <iostream>
#include <string>
using namespace std;

template<typename MyType>
MyType TripleMin(MyType item1, MyType item2, MyType item3) {
   MyType minVal = item1; // Holds min item value, init to first item

   if(item2 < minVal) {
      minVal = item2;
   }
   if(item3 < minVal) {
      minVal = item3;
   }

   return minVal;
}

int main() {
   int num1 = 55; // Test case 1, item1
   int num2 = 99; // Test case 1, item2
   int num3 = 66; // Test case 1, item3
   char let1 = 'a'; // Test case 2, item1
   char let2 = 'b'; // Test case 2, item2
   char let3 = 'c'; // Test case 2, item3
   string str1 = "aaa"; // Test case 3, item1
   string str2 = "bbb"; // Test case 3, item2
   string str3 = "ccc"; // Test case 3, item3

   // Try TripleMin function with ints
   cout << "Min: " << TripleMin(num1, num2, num3) << endl;
   // Try TripleMin function with chars
   cout << "Min: " << TripleMin(let1, let2, let3) << endl;
   // Try TripleMin function with strings
   cout << "Min: " << TripleMin(str1, str2, str3) << endl;

   return 0;
}
    

The function return type is preceded by template<typename TheType>, where TheType can be any identifier. That type is known as a template parameter and can be used throughout the function for any parameter types, return types, or local variable types. The identifier may be various items such as an int, double, char, or string, or a pointer or reference, or even another template parameter.

The compiler automatically generates a unique function definition for each type appearing in function calls to the function template. Thus, the above program would create three TripleMin function definitions using int, char, and string. The programmer never sees those function definitions.

Exercises: Function templates.

Exercise: Fill in the blank.

template<typename MyType>
  _____<3> MyType x, MyType y, MyType z) {
   return (x + y + z);
}
        

Answer: MyType. This function attempts to average i, j, and k of MyType, so should return the same type.

Exercise: Fill in the blank.

template<typename ____>
T TripleMin(T item1, T item2, T item3) { ... }
    

Answer: T. T is the identifier being used as the typename for this function template.

Exercise: For the earlier TripleMin function template, what happens if a call is TripleMin(i, j, k) but those arguments are of type long long?

Answer: The compiler creates a function with long long types and calls that function. Function template support various types.

Exercise: What happens if a call is TripleMin(i, j, k) but those arguments are of type string?

Answer: String is just another type, so the function will compare strings. Function templates support various types, including strings.

Exercise: What happens if a call is TripleMin(i, j, z) where i and j are ints, but z is a string?

Answer: The compiler will generate an error, because TheType must be the same for all three arguments. TheType is just one type. The compiler doesn't know how to generate a function for two ints and a string.

Programmers optimally may explicitly specify the type as a special argument, as in TripleMin<int>(num1, num2, num3);

Function template with multiple parameters.


template<typename T1, typename T2>
ReturnType FunctionName(Parameters) {
    ...
}
    

Earlier versions of C++ used the word "class" rather than "typename". Though misleading (the type need not be a class, but can be int or double, for example), the word class is still allowed for backwards compatibility, and much existing C++ code uses that word.

Exercise:

This program currently fails to compile. Modify TripleMin() so that item1 can be of a different type than item2 and item3.


Answer:


#include <iostream>
using namespace std;

template<typename TheType, TheType2>
TheType TripleMin(TheType item1, TheType2 item2, TheType2 item3) {
   TheType minVal = item1; // Holds min item value, init to first item

   if (item2 < minVal) {
      minVal = item2;
   }
   if (item3 < minVal) {
      minVal = item3;
   }
   return minVal;
}

int main() {
   int num1;
   int num2;
   int num3;
   double dbl1;

   num1 = 55;
   num2 = 99;
   num3 = 66;
   dbl1 = 12.5;
   
   cout << "Items: " << num1 << " " << num2 << " " << num3 << endl;
   cout << "Min: " << TripleMin(num1, num2, num3) << endl << endl;
   
   cout << "Items: " << dbl1 << " " << num2 << " " << num3 << endl;
   cout << "Min: " << TripleMin(dbl1, num2, num3) << endl << endl;
      
   return 0;
}
    
Exercise:

Define a generic function called CheckOrder() that checks if four items are in ascending, neither, or descending order. The function should return -1 if the items are in ascending order, 0 if the items are unordered, and 1 if the items are in descending order.

The program reads four items from input and outputs if the items are ordered. The items can be different types, including integers, strings, characters, or doubles.

Ex. If the input is


bat hat mat sat
63.2 96.5 100.1 123.5

the output is


Order: -1
Order: -1
    

Complete the following code:


Answer:


#include <string>
#include <iostream>
using namespace std;


template<typename T>
int CheckOrder(T item1, T item2, T item3, T item4) {
   // Check the order of the input: return -1 for ascending,
   // 0 for neither, 1 for descending
   if ((item1 < item2)
         && (item2 < item3)
         && (item3 < item4)) {
      return -1;
   }
   else if ((item1 > item2)
            && (item2 > item3)
            && (item3 > item4)) {
      return 1;
   }
   else {
      return 0;
   }
}

int main() {
   // Read in four strings
   string stringArg1, stringArg2, stringArg3, stringArg4;
   cin >> stringArg1;
   cin >> stringArg2;
   cin >> stringArg3;
   cin >> stringArg4;
   // Check order of four strings
   cout << "Order: " << CheckOrder(stringArg1, stringArg2, stringArg3, stringArg4) << endl;;

   // Read in four doubles
   double doubleArg1, doubleArg2, doubleArg3, doubleArg4;
   cin >> doubleArg1;
   cin >> doubleArg2;
   cin >> doubleArg3;
   cin >> doubleArg4;
   // Check order of four doubles
   cout << "Order: " << CheckOrder(doubleArg1, doubleArg2, doubleArg3, doubleArg4) << endl;;
   
   return 0;
}
    
Exercise: Min, max, median

Given the definitions of main(), Read() and Write(), complete the following code by implementing the following member functions:

template<typename TheType> vector<TheType> GetStatistics(const vector<TheType>& list) template<typename TheType> void Run(vector<TheType>& list)

Note: vector.size() returns a long unsigned int, not an int.

Hint: Use the built-in sort function to sort a vector.

Example Input:

1 3 5 9 7
2.2 3.3 1.1 4.4 5.5
one two three four five

Example Output:

1 3 5 7 9
1 5 9

1.1 2.2 3.3 4.4 5.5
1.1 3.3 5.5

five four one three two
five one two

Complete the following code:


Answer:


#include <iostream>
#include <string>
#include <vector>
#include <algorithm> // to use sort()
using namespace std;

const int NUM_VALUES = 5;

// Input NUM_VALUES of TheType into the vector parameter
template<typename TheType> void Read(vector<TheType>& list) {
   for (int j = 0; j < NUM_VALUES; ++j) {
      cin >> list.at(j);
   }
}

// Output the elements of the vector parameter
template<typename TheType> void Write(const vector<TheType>& list) {
   long unsigned int j;
   for (j = 0; j < list.size(); ++j) {
      cout << list.at(j) << " ";
   }
}

// Return the min, median, and max of the vector parameter in a new vector
template<typename TheType> vector<TheType> GetStatistics(const vector<TheType>& list) {
   vector<TheType> statistics(3);
   statistics.at(0) = list.at(0);
   statistics.at(1) = list.at(list.size() / 2);
   statistics.at(2) = list.at(list.size() - 1);
   return statistics;
}

// Read values into a vector, sort the vector, output the sorted vector,
// then output the min, median, and max of the sorted vector
template<typename TheType> void Run(vector<TheType>& list) {
   vector<TheType> statistics(3);
   Read(list);
   sort(list.begin(), list.end());
   Write(list);
   cout << endl;
   statistics = GetStatistics(list);
   Write(statistics);
   cout << endl;
}

int main() {
   vector<int> integers(NUM_VALUES);
   Run(integers);
   cout << endl;

   vector<double> doubles(NUM_VALUES);
   Run(doubles);
   cout << endl;

   vector<string> strings(NUM_VALUES);
   Run(strings);

   return 0;
}
    

Class templates

Multiple classes may be nearly identical, differing only in their data types. The following shows a class managing three int numbers, and a nearly identical class managing three short numbers.

Example: Classes may be nearly identical, differing only in data type.



class TripleInt {
public:
   TripleInt(int val1 = 0, int val2 = 0, int val3 = 0);
   void PrintAll() const; // Print all data member values
   int MinItem() const;   // Return min data member value
private:
   int item1;             // Data value 1
   int item2;             // Data value 2
   int item3;             // Data value 3
};

TripleInt::TripleInt(int i1, int i2, int i3) {
   item1 = i1;
   item2 = i2;
   item3 = i3;
}

// Print all data member values
void TripleInt::PrintAll() const {
   cout << "(" << item1 << "," << item2
        << "," << item3 << ")" << endl;
}

// Return min data member value
int TripleInt::MinItem() const {
   int minVal;

   minVal = item1; // Holds min item value, init to first item
   
   if (item2 < minVal) {
      minVal = item2;
   }
   if (item3 < minVal) {
      minVal = item3;
   }
   
   return minVal;
}


class TripleShort {
public:
   TripleShort(short val1 = 0, short val2 = 0, short val3 = 0);
   void PrintAll() const; // Print all data member values
   short MinItem() const; // Return min data member value
private:
   short item1;           // Data value 1
   short item2;           // Data value 2
   short item3;           // Data value 3
};

TripleShort::TripleShort(short i1, short i2, short i3) {
   item1 = i1;
   item2 = i2;
   item3 = i3;
}

// Print all data member values
void TripleShort::PrintAll() const {
   cout << "(" << item1 << "," << item2
        << "," << item3 << ")" << endl;
}

// Return min data member value
short TripleShort::MinItem() const {
   short minVal;
   
   minVal = item1; // Holds min item value, init to first item
   
   if (item2 < minVal) {
      minVal = item2;
   }
   if (item3 < minVal) {
      minVal = item3;
   }
   
   return minVal;
}
    

Writing and maintaining redundant classes that only differ by data type can be time consuming and error prone. C++ supports a better approach.

A class template is a class definition having a special type parameter that may be used in place of types in the class. A variable declared of that class type must indicate a specific type.

Example: A class template enables one class to handle various data types.


#include <iostream>
using namespace std;

template<typename TheType>
class TripleItem {
public:
   TripleItem(TheType val1 = 0, TheType val2 = 0, TheType val3 = 0);
   void PrintAll() const;   // Print all data member values
   TheType MinItem() const; // Return min data member value
private:
   TheType item1;           // Data value 1
   TheType item2;           // Data value 2
   TheType item3;           // Data value 3
};

template<typename TheType>
TripleItem<TheType>::TripleItem(TheType i1, TheType i2, TheType i3) {
   item1 = i1;
   item2 = i2;
   item3 = i3;
}

// Print all data member values
template<typename TheType>
void TripleItem<TheType>::PrintAll() const {
   cout << "(" << item1 << "," << item2
        << "," << item3 << ")" << endl;
}

// Return min data member value
template<typename TheType>
TheType TripleItem<TheType>::MinItem() const {
   TheType minVal = item1; // Holds value of min item, init to first item
   
   if (item2 < minVal) {
      minVal = item2;
   }
   if (item3 < minVal) {
      minVal = item3;
   }
   
   return minVal;
}

int main() {
   TripleItem<int> triInts(9999, 5555, 6666); // TripleItem class with ints
   TripleItem<short> triShorts(99, 55, 66);   // TripleItem class with shorts
   
   // Try functions from TripleItem
   triInts.PrintAll();
   cout << "Min: " << triInts.MinItem() << endl << endl;
   
   triShorts.PrintAll();
   cout << "Min: " << triShorts.MinItem() << endl << endl;
   
   return 0;
}

    

The class definition is preceded by template<typename TheType> where TheType can be any identifier. That type is known as a template parameter and can be used throughout the class, such as for parameter types, function return types, or local variable types. The identifier is known as a template parameter, and may be various items such as an int, double, char, string, a pointer or reference type, or even another template parameter.

Any of the class's functions or the constructors must also be preceded by the template declaration, and have the type angle brackets appended to the name as in TripleItem<TheType>::Print(). An object of this class can be declared by appending after the class's name a specific type in angle brackets, such as TripleItem<short> triShorts(99, 55, 66);.

Exercises: Class templates

Exercise: A class has been defined using the type GenType throughout, where GenType is intended to be chosen by the programmer when declaring a variable of this class. What should be the code that immediately precedes the class definition?

Answer: template<typename GenType>

Exercise: A key advantage of class templates is relieving the programmer from writing redundant code that differs only by type.

Answer: True. Type-independent code can significantly improve code reuse. The vector class template is a great example -- the same code handles numerous different types, like int, string, and even user-defined types.

Exercise: For a class template defined as

template<typename T> 
class Vehicle { ... }
, what are the appropriate variable declaration(s) of that class?

Answer: Vehicle<int> v1, and Vehicle<short> v1. "T" is the generic type used in the class template, acting as a placeholder for the actual type. A variable declaration must now specify the actual type

A template may have multiple parameters, separated by commas.

Class template with multiple parameters.


template<typename T1, typename T2>
class ClassName {
}
    

Earlier versions of C++ used the word "class" rather than "typename". Though misleading (the type need not be a class, but can be int or double, for example), the word class is still allowed for backwards compatibility, and much existing C++ code uses that word.

Exercise: Class templates.

Exercise:

Modify the TimeHrMn class to utilize a class template. Note that the main() function passes int and double as parameters for the SetTime() member function.


Answer:


#include <iostream>
using namespace std;

template<typename T>
class TimeHrMn {
public:
   void SetTime(T userMin);
   void PrintTime() const;
private:
   int hrsVal;
   T minsVal;
};

template<typename T>
void TimeHrMn<T>::SetTime(T userMin) {
   minsVal = userMin;
   hrsVal = userMin / 60.0;
}

template<typename T>
void TimeHrMn<T>::PrintTime() const {
   cout << "Hours: " << hrsVal << " ";
   cout << "Minutes: " << minsVal << endl;
}

int main() {
   TimeHrMn<int> usrTimeInt;
   TimeHrMn<double> usrTimeDbl;

   usrTimeInt.SetTime(135);   
   usrTimeInt.PrintTime();

   usrTimeDbl.SetTime(135.0);  
   usrTimeDbl.PrintTime();

   return 0;
}
            
Exercise: Zip code and population

Define a class StatePair with two template types (T1 and T2), constructors, mutators, accessors, and a PrintInfo() method.

Three vectors have been pre-filled with StatePair data in main():

Complete main() to use an input Zip code to retrieve the correct state abbreviation from the vector zipCodeState. Then use the state abbreviation to retrieve the state name from the vector abbrevState. Lastly, use the state name to retrieve the correct state name/population pair from the vector statePopulation and output the pair.

Example Input:

21044

Example Output:

Maryland: 6079602

Download zip code and population data (abbreviation_state.txt, state_population.txt, zip_code_state.txt) from here by clicking on "Download ZIP"

Answer:

StatePair.h

#ifndef STATEPAIR
#define STATEPAIR

using namespace std;

template<typename T1, typename T2>
class StatePair {
public:
   StatePair();
   StatePair(T1 userKey, T2 userValue);
   void SetKey(T1 newKey);
   void SetValue(T2 newVal);
   T1 GetKey();
   T2 GetValue();
   void PrintInfo();
private:
   T1 key;
   T2 value;
};

template<typename T1, typename T2>
StatePair<T1, T2>::StatePair(){}

template<typename T1, typename T2>
StatePair<T1, T2>::StatePair(T1 userKey, T2 userValue) {
      key = userKey;
      value = userValue;
}

template<typename T1, typename T2>
void StatePair<T1, T2>::SetKey(T1 newKey) {
   key = newKey;
}

template<typename T1, typename T2>
void StatePair<T1, T2>::SetValue(T2 newVal) {
   value = newVal;
}

template<typename T1, typename T2>
T1 StatePair<T1, T2>::GetKey() {
   return key;
}

template<typename T1, typename T2>
T2 StatePair<T1, T2>::GetValue() {
   return value;
}

template<typename T1, typename T2>
void StatePair<T1, T2>::PrintInfo() {
   cout << key << ": " << value << endl;
}

#endif

main.cpp

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include "StatePair.h"
using namespace std;

int main() {
    ifstream inFS;
    int zip, population;
    string abbrev, state;
    unsigned int i;
    
    vector<StatePair <int, string>> zipCodeState;
    vector<StatePair<string, string>> abbrevState;
    vector<StatePair<string, int>> statePopulation;
    
    inFS.open("zip_code_state.txt");
    if (!inFS.is_open()) {
        cout << "Could not open file zip_code_state.txt." << endl;
        return 1;
    }
    while (!inFS.eof()) {
        StatePair <int, string> temp;
        inFS >> zip;
        if (!inFS.fail()) temp.SetKey(zip);
        inFS >> abbrev;
        if (!inFS.fail()) temp.SetValue(abbrev);
        zipCodeState.push_back(temp);
    }
    inFS.close();
    
    inFS.open("abbreviation_state.txt");
    if (!inFS.is_open()) {
        cout << "Could not open file abbreviation_state.txt." << endl;
        return 1;
    }
    while (!inFS.eof()) {
        StatePair <string, string> temp;
        inFS >> abbrev;
        if (!inFS.fail()) temp.SetKey(abbrev);
        getline(inFS, state);
        getline(inFS, state);
        state = state.substr(0, state.size()-1);
        if (!inFS.fail()) temp.SetValue(state);
        abbrevState.push_back(temp);
    }
    inFS.close();
    
    inFS.open("state_population.txt");
    if (!inFS.is_open()) {
        cout << "Could not open file state_population.txt." << endl;
        return 1;
    }
    while (!inFS.eof()) {
        StatePair <string, int> temp;
        getline(inFS, state);
        state = state.substr(0, state.size()-1);
        if (!inFS.fail()) temp.SetKey(state);
        inFS >> population;
        if (!inFS.fail()) temp.SetValue(population);
        getline(inFS, state);
        statePopulation.push_back(temp);
    }
    inFS.close();
    
    cin >> zip;
    for (i = 0; i < zipCodeState.size(); i++)
        if (zipCodeState.at(i).GetKey() == zip)
            abbrev = zipCodeState.at(i).GetValue();
    
    for (i = 0; i < abbrevState.size(); i++)
        if (abbrevState.at(i).GetKey() == abbrev)
            state = abbrevState.at(i).GetValue();
    
    for (i = 0; i < statePopulation.size(); i++)
        if (statePopulation.at(i).GetKey() == state)
            statePopulation.at(i).PrintInfo();
    
    return 0;
}

Exploring further:

Your exercise answers are saved to this Google doc