The type repetition problem

Suppose you're building a game inventory system that tracks item quantities:

struct IntSlot
{
    int item{};
    int quantity{};
};

This works for items identified by integer IDs. But your UI needs string-based item names:

struct StringSlot
{
    std::string item{};
    int quantity{};
};

Now you have two nearly identical structs. Adding support for item enums means a third. The pattern repeats: same structure, different types.

Class templates solve this

A class template is a blueprint for generating struct and class definitions. You define the pattern once, and the compiler creates specialized versions for each type you need.

template <typename T>
struct Slot
{
    T item{};
    int quantity{};
};

Now you can create slots holding any item type:

Slot<int> byId{ 42, 5 };             // item is int
Slot<std::string> byName{ "sword", 1 }; // item is std::string

Defining a class template

Start with the template parameter declaration, then define the struct using the placeholder type:

template <typename T>
struct Container
{
    T value{};
    bool hasValue{ false };
};

The syntax mirrors function templates:

  • template <typename T> declares the placeholder type
  • T can be used wherever you need a flexible type
  • Members can mix template and concrete types

Instantiating class templates

Specify the type in angle brackets when creating objects:

#include <iostream>
#include <string>

template <typename T>
struct Container
{
    T value{};
    bool hasValue{ false };
};

int main()
{
    Container<int> count{ 42, true };
    Container<double> measurement{ 98.6, true };
    Container<std::string> message{ "Hello", true };

    std::cout << count.value << '\n';        // 42
    std::cout << measurement.value << '\n';  // 98.6
    std::cout << message.value << '\n';      // Hello

    return 0;
}

The compiler generates three distinct types:

  • Container<int> with an int value member
  • Container<double> with a double value member
  • Container<std::string> with a std::string value member

What the compiler generates

When you write:

Container<int> count{ 42, true };

The compiler produces something equivalent to:

struct Container_int
{
    int value{};
    bool hasValue{ false };
};

Container_int count{ 42, true };

Each unique template argument creates one type definition. Container<int> and Container<double> are completely separate types that happen to share a structural pattern.

Using class templates with function templates

Class templates often pair with function templates:

#include <iostream>

template <typename T>
struct Range
{
    T low{};
    T high{};
};

template <typename T>
T midpoint(Range<T> r)
{
    return (r.low + r.high) / 2;
}

int main()
{
    Range<int> scores{ 0, 100 };
    Range<double> temperatures{ -40.0, 120.0 };

    std::cout << midpoint(scores) << '\n';       // 50
    std::cout << midpoint(temperatures) << '\n'; // 40

    return 0;
}

The function template's T matches the class template's T, allowing seamless type deduction.

Multiple template parameters

Class templates can have multiple type parameters:

template <typename K, typename V>
struct Entry
{
    K key{};
    V value{};
};

Usage:

Entry<int, std::string> userById{ 1001, "Alice" };
Entry<std::string, double> priceByName{ "apple", 1.99 };
Entry<char, int> asciiValue{ 'A', 65 };

Each parameter resolves independently, allowing different combinations.

Functions accepting multi-parameter templates

Function templates working with multi-parameter class templates need matching declarations:

#include <iostream>

template <typename K, typename V>
struct Entry
{
    K key{};
    V value{};
};

template <typename K, typename V>
void display(Entry<K, V> e)
{
    std::cout << e.key << " -> " << e.value << '\n';
}

int main()
{
    Entry<std::string, int> score{ "Player1", 2500 };
    display(score);  // Player1 -> 2500

    return 0;
}

Generic function parameters

Sometimes you want a function that works with any type having certain members:

template <typename T>
void showKeyValue(T item)
{
    std::cout << item.key << ": " << item.value << '\n';
}

This works with any type that has key and value members, not just Entry:

struct Config
{
    std::string key{};
    int value{};
};

Entry<std::string, double> setting{ "volume", 0.75 };
Config option{ "maxPlayers", 8 };

showKeyValue(setting);  // Works with Entry
showKeyValue(option);   // Works with Config too

The standard library's std::pair

The standard library provides std::pair<T, U> for paired values:

#include <utility>
#include <iostream>

int main()
{
    std::pair<std::string, int> player{ "Alice", 100 };

    std::cout << player.first << ": " << player.second << '\n';

    return 0;
}

Members are named first and second. Use std::pair instead of writing custom pair types.

Class templates in header files

Place class templates in headers for use across multiple files:

container.h:

#pragma once

template <typename T>
struct Container
{
    T value{};
    bool hasValue{ false };
};

template <typename T>
bool isEmpty(Container<T> c)
{
    return !c.hasValue;
}

main.cpp:

#include "container.h"
#include <iostream>

int main()
{
    Container<int> data{ 42, true };
    std::cout << isEmpty(data) << '\n';  // 0 (false)

    return 0;
}

Templates are exempt from the one-definition rule, so including them in multiple files works correctly.

Summary

Concept Syntax
Single-type template template <typename T> struct Name { T member; };
Multi-type template template <typename T, typename U> struct Name { T a; U b; };
Instantiation Name<int> obj{ value };
Function with template param template <typename T> void func(Name<T> param)

Key points:

  • Class templates generate type definitions from a single pattern
  • Each unique template argument set produces a distinct type
  • Template parameters can mix with concrete types in the same struct
  • Function templates naturally pair with class templates
  • std::pair<T, U> is the standard library's general-purpose pair type
  • Place templates in headers for multi-file usage