Class Templates

An introduction to class templates in C++, explaining why we need them and how to create them
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
Ryan McCombe
Ryan McCombe
Posted

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.

What Are Templates

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.

Diagram showing the difference between class templates, classes and objects

Creating Class Templates

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;
};

Using Class Templates

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;
}

Class Templates with Multiple Template Parameters

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 };

Nested Template Types

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>>;

Class Template Argument Deduction (CTAD)

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.

Struct Templates

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.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Templates
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Function Templates

An introduction to C++ variable templates, why we need them, and how to create them.
DreamShaper_v7_fantasy_female_pirate_Sidecut_hair_black_clothi_1.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved