Templates

Class Templates

One most used example is when you need to have a pair of values of the same type. It is just too frequent to write a struct for each type. Here's what we get for integers.

#include <iostream>

// Define the members that belong to this type
struct Pair
{
    int first{};
    int second{};
};

int main()
{
    Pair p{ 3, 7 };
    std::cout << p.first << ' ' << p.second << '\n';
    return 0;
}

Where the struct Pair has two members of type int named first and second: A value initialization using the empty braces initializes both members to 0. Then in main, the object p is created by using a value initialization with arguments 3 and 7. The only insertion statement outputs both members of p with a space between them. The program is simple and works, but it is limited to integers.

A second version is needed as soon as you require floating point numbers.

#include <iostream>

struct Pair
{
    int first{};
    int second{};
};

struct DoublePair
{
    double first{};
    double second{};
};

int main()
{
    Pair p{ 3, 7 };
    std::cout << p.first << ' ' << p.second << '\n';

    DoublePair d{ 1.5, 4.2 };
    std::cout << d.first << ' ' << d.second << '\n';
    return 0;
}

Now we have two almost identical structs. The only variance is in the member types. Each new type would require yet another copy of the same layout. This duplication is the source of the problem that a class template saves us from.

The class template only defines the layout, and lets the compiler generate the concrete type.

#include <iostream>

// Make the following definition depend on template arguments
template <typename T>
struct Pair
{
    T first{};
    T second{};
};

int main()
{
    Pair<int> p{ 3, 7 };
    std::cout << p.first << ' ' << p.second << '\n';

    Pair<double> d{ 1.5, 4.2 };
    std::cout << d.first << ' ' << d.second << '\n';
    return 0;
}

The line template <typename T> is the template parameter declaration. It indicates to the compiler that what comes after this line is not fully defined yet, that T will be eventually replaced by the name of a type. The pattern itself; both members are declared with the type T and are value initialized with empty braces.

When the compiler encounters the declaration Pair<int> p{ 3, 7 } it realizes that the template argument is int. It thus replaces all instances of T by int and produces a concrete struct type with two int data members. It instantiates the class template with this generated type.

The object p is then created using the generated class template type and initialized with the passed values.

The next insertion then prints out the two data members as before.

The second, separate substitution occurs with the later declaration Pair<double> d{ 1.5, 4.2 }; where T is replaced by double. This results in a different struct type with both members being doubles. Each template argument produces a different concrete type, the two instantiations don't share any storage.

Since the template pattern doesn't produce any code until a specific type is requested, the first time an argument is used in a template, the compiler produces the relevant struct. Any subsequent uses of that argument load the pre generated type.

The symbol T is merely a standard name for a single unconstrained type parameter (although any valid identifier can be used); the keyword typename is used to specify that the parameter represents a type. The older keyword class is also permitted for this purpose.

A class template can be instantiated for any type that can be a member. The members themselves place no additional restrictions beyond being copiable/movable in the normal way. If a type is not usable as a member the compiler just does not generate the instantiation and reports an error where it is used.

This pattern also applies to other simple aggregates. Here is a general template that holds three values of the same type.

#include <iostream>

template <typename T>
struct Triple
{
    T first{};
    T second{};
    T third{};
};

int main()
{
    Triple<int> t{ 10, 20, 30 };
    std::cout << t.first << ' ' << t.second << ' ' << t.third << '\n';

    Triple<char> c{ 'a', 'b', 'c' };
    std::cout << c.first << ' ' << c.second << ' ' << c.third << '\n';
    return 0;
}

The template parameter T again stands for the type which will be supplied later on. The first use, Triple<int>, causes the compiler to produce a struct type whose three members are all int, the object t is created with the 3 ints and the insertion prints them. The second use produces a separate struct whose members are all char, the object c is created from that type, and the second insertion prints the 3 characters.

In both cases the angle brackets after the template name are a must. They provide the actual type for T. Without those brackets the compiler wouldn't know which instantiation to generate.

Still, the principal argument is the same as for function templates: a class template can never be an object itself. Instead, it is a template, a description that the compiler materializes into real types when they are required. When they exist, the concrete types are just as for the classical case where the struct is hand written, but the source code only has the layout in it once.

Exercise: Class TemplatesStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Class Templates

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Replace Pair and DoublePair with a single Pair<T> class template. Preserve the original integer and double values and create another integer pair (9,11).

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

struct Pair
{
    int first{};
    int second{};
};

struct DoublePair
{
    double first{};
    double second{};
};

int main()
{
    Pair p{ 3, 7 };
    std::cout << p.first << ' ' << p.second << '\n';

    DoublePair d{ 1.5, 4.2 };
    std::cout << d.first << ' ' << d.second << '\n';
    return 0;
}

Hints

Hint 1

Pair and DoublePair differ only in their member types. A class template can use T for both first and second instead of maintaining two definitions.

Hint 2

After defining template <typename T> struct Pair, initialize Pair<int> and Pair<double>. Each instantiation still has two members of its selected type.

Worked solution

Pair becomes a class template whose two members share T. Pair<int> and Pair<double> replace the separate concrete types, and an additional Pair<int> holds 9 and 11 independently. These simple aggregates have no custom member functions.

#include <iostream>
template <typename T>
struct Pair
{
    T first{};
    T second{};
};
int main()
{
    Pair<int> p{3, 7};
    Pair<double> d{1.5, 4.2};
    Pair<int> second{9, 11};
    std::cout << p.first << ' ' << p.second << '\n';
    std::cout << d.first << ' ' << d.second << '\n';
    std::cout << second.first << ' ' << second.second << '\n';
}

Example output

3 7
1.5 4.2
9 11

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.