Introduction
In the previous tutorial, we used the STL (Standard Template Library) implementations of dynamic lists. More specifically, we looked at the forward_list, the list, and the vector. We showed how to use them and talked about which scenarios are best suited for which type of list.
In this tutorial, we are going to go over how the STL dynamic lists can store any type of data type. It does this through the use of templates.
Code
So, let’s jump into an example of writing a function to add two numbers together:
template <class T>
T Add(T a, T b)
{
return a + b;
}
The template command tells the compiler that you want the function to take in any data type. You also give the data type a name, in this case we call it T. We give it a name so that it is easier to refer to it later. So, now we declare the function using the new stand in data type called T. We can treat this new type T as though it were a normal data type.
Now we can call our new function like this:
int main()
{
double d1 = 2.3;
double d2 = 3.4;
cout << Add(d1, d2) << endl;
int i1 = 4;
int i2 = 5;
cout << Add(i1, i2) << endl;
return 0;
}
What is exciting about this is that we wrote one function, and it was able to handle both int and double data types. Without templates, we would have to write two functions to do this:
double Add(double a, double b)
{
return a + b;
}
int Add(int a, int b)
{
return a + b;
}
This can be very repetitive. So, templates were created to save you from repeating the same code over and over with different input types. Templates also work on classes:
template <class T>
class Complex
{
public:
T Real;
T Imag;
Complex(T real, T imag)
{
Real = real;
Imag = imag;
}
};
This allows you to create a class that can store any data type. In the case of a list, this can be a huge life saver. No one wants to write the exact same code to create two lists, one that handles int and one that handles double! It doesn’t matter what kind of object is being stored, we just want a way to store it.
Then you would create a variable of the class like this:
Complex<double> cmplxDbl(1.1, 2.2);
Complex<int> cmplxInt(3, 4);
This should look very familiar to the way that the STL classes were created in the last tutorial.
What is really going on behind the scenes is that the compiler is generating the templated methods and classes for you. So, the compiler sees a method called Add that has a template parameter of T. It then waits to see how the Add method is used. When it comes across the first call to Add with two doubles as the arguments, it generates a method for us that has two doubles as input arguments. Much like the code sample above of the function that we would have written without templates. Then when it comes across the call to Add with two ints as input arguments, it generates a method for that. This means that the compiler is doing the repetitive work of creating two methods with the same logic but different types for us. The compiler also just creates the functions that we need, meaning that it doesn’t create one for say floats until we need it.
Buyer Beware
The template’s ability to take in any data type is a double edged sword though. Take this for example:
template <class T>
T Mult(T a, T b)
{
return a * b;
}
class Complex
{
public:
double Real;
double Imag;
Complex(double real, double imag)
{
Real = real;
Imag = imag;
}
};
class Matrix2x2
{
public:
double M00;
double M01;
double M10;
double M11;
Matrix2x2(double m00, double m01, double m10, double m11)
{
M00 = m00;
M01 = m01;
M10 = m10;
M11 = m11;
}
};
int main()
{
Complex c1(1.1, 2.2);
Complex c2(3.3, 4.4);
Complex c3 = Mult(c1, c2);
Matrix2x2 m1(1, 2, 3, 4);
Matrix2x2 m2(2, 4, 6, 8);
Matrix2x2 m3 = Mult(m1, m2);
return 0;
}
At first glance, it looks harmless enough. We know how to multiply together two complex numbers or two 2x2 matrices. No problem. Well, it won’t compile. You’ll get an error message saying that it doesn’t know how to apply the * operator to those objects. If we think about it the complier is right. We just happened to call our class Complex and give it two variables named Real and Imag. How is the compiler supposed to tell this class from any other class?
The 2x2 matrix class is slightly more interesting because there are two ways to multiply together matrices that makes sense. We could either do matrix multiplication or we could multiply them together element-wise. Even if the compiler was able to discern that it was a matrix, it still wouldn’t be able to tell how you wanted them multiplied together.
So, that’s the big benefit and the big problem: any data type can be passed in. This can make it difficult to design a class or method that gets some work done, but is flexible enough to handle a wide variety of input data types. As we have seen above, even something as simple as addition or multiplication is difficult to make work over a wide range of inputs.
Summary
In this tutorial, we talked about templates, which are a way to write a function or class once and then have the compiler create versions of it for every data type that you want passed in.
In the next tutorial, we are going to talk about inheritance. Inheritance is when one class inherits all of the properties and methods of its parent. This is a very powerful tool to help reduce the number of lines of code and to make the code more clear. It also can help with the template problem we had above. Instead of using a template, which means that any data type can be passed in, we can use a class, which means that only that type or classes that inherit from that class can be passed in. We can use this as a way to make our function work on many classes, but at the same time rest assured that they have some minimal set of properties and methods, so that they can work properly.
If you have any questions or comments about what was covered here, post them to the comments. I watch them closely and will respond and try to help you out.