Using Templates to Write Generic Functions

A (very) brief introduction to using the template keyword for implementing generic functions in C++11 (and up!)

Generics

The situation often arises where we need a function that works with an array, but the situation arises where we dont know what type of array it will be. Is it an array of integers? floating point numbers? Is it an array of chars? In days past we were left with two options; Have an army of prodcedures like printIntArray(int arr[], int n), printCharArray(char arr[], int n), etc., etc., or work with C’s flimsy void* type, with its own inherit risks. Thankfully, C++ abolished the void* type, and introduced Generics via its powerful template system.

If you’ve worked with any of the container classes in C++, such as std::vector or std::map than you’ve been exposed to the concept of generics via the Standard Template Library.

The same function that allows us to use multiple types with std::vector, allows us to write functions that works with multiple types, and the syntax is incredibly simple.

Working With Templates

We will continue with out example of needing to print multiple vector types. If we have a vector of int’s and we’re going to need to print its contents to the console multiple times during program execution, it would make sense to write a function like:

void printVec(std::vector<int> vec)
{
  for (int v : vec)
  {
    cout<<v<<" ";
  }
  cout<<endl;
}

Great, now we can print our vector by just using the function call printVec(vector_name) whenever we need to output our vector of integers. But what happens if the situation arises where we will also have vectors of floats? Our printVec function no longer works. However, with the power of templates we can adapt our printVec function to work with ANY type:

template <typename T>

void printVec(std::vector<T> vec)
{
   for (auto v : vec)
   {
     cout<<v<<" ";
   }
   cout<<endl;
}

By using the keyword ‘template <typename T>’ we’ve let the compiler know that this is a template function, and that the compiler should deduce what type, ‘T’ is, we’ve also used the ‘auto’ keyword letting the compiler once again know that it must deduce the type for us. In this way we can call printVec(vector_name) with a vector  containing any type that can be directly sent to an output stream. Para examplar:

template <typename T>
void printVec(vector<T> vec)
{
   for (auto i = vec.begin(); i != vec.end(); i++)
   {
     cout<<*i<<" ";
   }
   cout<<endl;
}

int main()
{
  vector<int> tosort = {1,47,69,22,101,13,34,2,7,4};
  printVec(tosort);
  vector<char> chr = {'a', ' ', 'c','h','a', 'r', ' ', 'v','e','c','t','o','r'};
  printVec(chr);
  vector<string> str = {"a", "vector", "of", "strings"};
  printVec(str);
  return 0;
}

Output:
1 47 69 22 101 13 34 2 7 4 
a   c h a r   v e c t o r 
a vector of strings 

We can also use templates to print non-STL collections, such as C-style arrays:

template <typename T>
void printArr(T arr[], int n)
{
  for (int i = 0; i < n; i++)
  {
    cout<<arr[i]<<" ";
  }
  cout<<endl;
}

int main()
{
  int nums[] = {1, 35, 16, 17, 8};
  printArr(nums, 5);
  char *astring = "A string";
  printArr(astring, strlen(astring));
  return 0;
}

Output:
1 35 16 17 8 
A   s t r i n g 

Notice now however that we have to pass a size parameter as we’re not working with a container that has a .size(), or default begin() and end() iterators. It’s still leaps and bounds ahead of the old school procedural method of writing a different procedure for every type!

Speaking of begin() and end() iterators, lets up the ante a rev the engine on our template example

Working with generic… Iterators

So now that we see we can handle all sorts of vectors with one function, but what if want to take it one further?

The STL implementation of sort() takes as its input two Read/Write iterators. Beyond that, it doesn’t specify variable type, or collection type, just iterator type. Shouldn’t be too hard. Can we do that our selves? You betcha.We’re going to implement our own sort() using the selection sort algorithm modified to use iterators instead of array indexes:

//generic container selection sort
template <typename Iterator>
void selectionSort(Iterator begin, Iterator end)
{
  for (auto i = begin; i != end; i++)
  {
    auto small = i;
    for (auto p = i; p != end; p++)
      if (*p < *small)
        small = p;
      if (*small < *i)
        swap(*i, *small);
  }
}

 

Right now you might be thinking: “Ok max, pump the breaks. Now your just messing with us. That cant possibly work!”

I said we were upping the ante, how about five bucks?

int main()
{
   std::vector<int> a = {1,37,12,17,9,64,5};
   printVec(a);
   selectionSort(a.begin(), a.end());
   printVec(a);
   return 0;
}

Output:
1 37 12 17 9 64 5 
1 5 9 12 17 37 64 

Told yah. And as you get excited about all the possibilities flowing through your head, remember: this has been just a small taste of what can be done with templates, go experiment for yourself and see the type of power you can wield with C++ templates!

The source code for the examples shown in in this article are available on my github at:

https://github.com/maxgoren/examples/blob/main/templates.cpp

Leave a Reply

Your email address will not be published. Required fields are marked *