Previously, we've seen the tokens <
and >
appearing in our C++ code. For example, this occurred when we were using std::pair
:
std::pair<int, bool> MyPair { 42, false };
When we see <
and >
in C++ code, this relates to the idea of templates, which we'll cover in this section.
We can think of a template as a section of code that contains temporary placeholders. This code can be a variable, a function, a class, and more.
So, for example, std::pair
is a template class. This gives std::pair
the flexibility it needs to be useful. Were we to try to create std::pair
as a simple class, we would need to define what type of object it will store in its first
and second
 fields.
This would be problematic. We would need to create a new pair class every time we want to store a different type of object in it.
Templates are the solution to this. We can think of templates as recipes that the compiler can use to create new variables, functions, or classes.
For example, we create a class template, and the compiler can use that to create classes. This creation happens automatically, at compile time.
The code for a class template will look very similar to the code we’d write to create a class. The main difference is that a class template will leave placeholders, called template parameters, within the definition.
For example, within the std::pair
class template, we don’t know what type the first
and second
variables will have. So, instead of specifying types there, we instead use template parameters, as placeholders for where the types will eventually be.
Given that the code contains placeholders, a class template cannot be directly used to create objects.
We first need to use the class template to create a class. We do that using the <
and >
 syntax.
For example, to create a class that can store an int
and a bool
, we used this syntax:
std::pair<int, bool>
When our compiler then sees us using a statement like this, it will use our std::pair
class template, replacing the placeholders to create a class the stores an int
and bool
.
This creates a class - which is a type. We could then use that type to create objects:
std::pair<int, bool> MyPairA { 4, true };
std::pair<int, bool> MyPairB { 41, false };
std::pair<float, int> MyPairC { 1.3f, 2 };
After running this code, we will have two classes: a class designed to store an int and a bool, and a class designed to store a float and an int. We will have two objects that are instances of the first class, and one object that is an instance of the second class.
Let's create our own pair class, to see how templates can help us.
To create a class for storing two integers, we would do this:
class Pair {
public:
int first;
int second;
};
To convert this to a template class, which we could use to create classes for storing any type, we make the following changes:
template <typename T>
class Pair {
public:
T first;
T second;
};
First, we added template <typename T>
before our class definition. This tells the compiler that Pair
is a template class, and that we will be using a template type. In this example, we’re calling our template type T
, but we could use any name we want, subject to normal variable naming restrictions:
template <typename MyType>
Secondly, we update our class definition to use our new templated type anywhere we would normally use the type name. This includes variable types as shown above:
T first;
T second;
But we can also use this type name anywhere a type is normally expected. This could be the return and parameter types of functions, for example. The following shows an alternative implementation of pair
, where we have class functions using the templated type name:
template <typename T>
class Pair {
public:
T GetFirst() { return first; }
T GetSecond() { return second; }
private:
T first;
T second;
};
The fact that we’re using a template type name does not restrict us from using regular types as well. We can mix them as needed. In the following example, second
is always an int
:
template <typename T>
class Pair {
public:
T GetFirst() { return first; }
int GetSecond() { return second; }
private:
T first;
int second;
};
We can use <
and >
characters to pass types into our template parameters, thereby creating a class from our class template.
Pair<int> MyPair { 1, 2 };
As with any type, expressions like this can show up anywhere a type is expected:
Pair<int> GetPair() {
return Pair<int> { 1, 2 };
}
int AddPair(Pair<int> Numbers) {
return Numbers.first + Numbers.second;
}
The previous example imposes a limitation where the first
and second
properties of our pair must be the same type. But, our class templates can have multiple template parameters. We separate them with a comma:
template <typename T, typename U>
class Pair {
public:
T first;
U second;
};
To create classes from this template, we would pass both template types between the <
and >
:
Pair<int, bool> MyPair { 42, true };
It is possible to pass a templated type into another templated type.
For example, the following type is a pair whose first type is an int
, and whose second type is another pair, comprising a float
and a bool
:
Pair<int, Pair<float, bool>>
This syntax might seem confusing, but it follows the same logic as calling a function to create an argument for another function. We just need to be cognizant of where the parenthesis are, and decompose them from the inside out:
Add(1, Add(2, 3));
Remember, if a type gets complex or difficult to understand, we can alias it to something simpler and more descriptive:
using HelpfulName = Pair<int, Pair<float, bool>>;
When working with templates, it is not always necessary to explicitly state the template types. For example, when we’re initializing an object, the compiler can sometimes infer from the constructor arguments what the template types are. For example, imagine we have a situation like this:
std::pair<int, bool> MyPair { 42, true };
Here, the arguments passed to the constructor (42
and true
) match the types passed to the template class (int
and bool
). So, we can omit the types, and let the compiler infer them for us:
std::pair MyPair { 42, true };
This is referred to as Class Template Argument Deduction or CTAD. Just because this is possible, it doesn’t mean it should always be used. It broadly has the same benefits and drawbacks as any other form of automatic type deduction (eg, the auto
keyword) so opinions vary on if and how it should be used.
Everything covered here also works with structs.
template <typename T, typename U>
struct Pair {
T first;
U second;
};
Given the similarity between structs and classes in C++, struct templates typically aren’t considered distinct entities. The term “class template” is used even if the type is actually a struct.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.