TeqStudy ยท C++ Track ยท Phase 09

Templates

Function and class templates, type deduction, multiple parameters, and specialization โ€” the actual mechanism behind every std::vector and std::sort you've used since Phase 7.

๐Ÿงฉ ๐Ÿ”ง ๐Ÿ“ฆ ๐ŸŽฏ ๐Ÿ
Course Progress
0%
MODULE 01

๐Ÿงฉ Why Templates Exist

You've already met this exact problem in disguise, back in Phase 3's function overloading. Templates are a genuinely more powerful solution to that same underlying issue.

๐Ÿ˜– The Problem โ€” Writing the Same Function for Every Type
C++ โ€” genuinely repetitive
int getMax(int a, int b) { return (a > b) ? a : b; }
double getMax(double a, double b) { return (a > b) ? a : b; }
std::string getMax(std::string a, std::string b) { return (a > b) ? a : b; }
// the LOGIC is identical every time โ€” only the type actually changes

Phase 3's function overloading (Module 8) lets these share one NAME, but you still had to write the SAME logic three separate times, once per type. If you needed getMax for 10 different types, you'd write it 10 times โ€” genuinely wasteful, and a maintenance risk if you ever need to fix a bug in the logic.

๐Ÿงฉ Templates โ€” Write the Logic Once, for Any Type
C++
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

getMax(5, 3);            // works for int
getMax(5.5, 3.2);        // works for double
getMax(std::string("b"), std::string("a"));  // works for string

A template lets you write ONE version of a function (or class, Module 3) that works for genuinely any type โ€” T is a placeholder the compiler fills in automatically based on what you actually call it with. One piece of logic, reused everywhere, with zero duplication.

๐Ÿ’ก
You've already been using templates constantly, without necessarily noticingPhase 7's std::vector<int>, std::map<std::string, int> โ€” the STL's ENTIRE container library is built from templates. std::vector<T> is a template that works identically whether T is int, std::string, or your own custom class. This phase teaches you to write that same kind of genuinely reusable code yourself.
๐Ÿง  Quick Check
What core problem do templates solve, compared to writing separate overloaded functions for each type?
MODULE 02

๐Ÿ”ง Function Templates

A closer, more careful look at exactly how the syntax from Module 1 works, piece by piece.

๐Ÿ” Breaking Down the Syntax
C++
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}
PartMeaning
template <typename T>"What follows is a template. T is a placeholder for a type, to be filled in later."
T getMax(T a, T b)A regular function declaration (Phase 3 Module 6), except T is used wherever a concrete type would normally go
๐Ÿ’ก
typename vs. class โ€” genuinely interchangeable hereYou'll also see template <class T> in real code โ€” for this basic use, typename and class mean exactly the same thing. typename is generally considered the clearer, more modern convention.
โš™๏ธ What the Compiler Actually Does

When you call getMax(5, 3), the compiler looks at the arguments, sees they're both int, and generates a real, concrete version of the function with T replaced by int โ€” as if you'd hand-written the int version yourself. Call it again with doubles, and it generates a SEPARATE concrete version for double. This process is called instantiation.

๐Ÿง  Quick Check
When you call getMax(5, 3) with a function template, what does the compiler actually do?
MODULE 03

๐Ÿ“ฆ Class Templates

The same idea from Module 1, applied to entire classes instead of just functions โ€” exactly how std::vector itself is actually built.

๐Ÿ“ฆ A Class That Works With Any Type
C++
template <typename T>
class Box {
private:
    T contents;
public:
    Box(T value) : contents(value) {}
    T getContents() { return contents; }
};

Box<int> intBox(42);
Box<std::string> strBox("Hello");

std::cout << intBox.getContents();   // 42
std::cout << strBox.getContents();   // Hello

This is precisely Phase 5's class syntax, with template <typename T> added and T used wherever a specific type would normally appear. Box<int> and Box<std::string> are genuinely different, separate types once instantiated โ€” but they share one single piece of source code.

๐Ÿ”— This Is Genuinely How std::vector Works

std::vector itself is essentially a (far more sophisticated) class template โ€” that's exactly why std::vector<int> and std::vector<std::string> both work identically, using the exact same underlying code, just instantiated for different types. You now understand the actual mechanism behind syntax you've been using since Phase 7.

๐Ÿง  Quick Check
Are Box<int> and Box<std::string> the same type, or different types?
MODULE 04

๐Ÿ” Template Type Deduction

You've been writing getMax(5, 3) without ever explicitly stating T is int. Here's exactly how the compiler figures that out.

๐Ÿ” The Compiler Infers T From Your Arguments
C++
getMax(5, 3);           // compiler infers T = int
getMax<int>(5, 3);    // explicitly stating T = int โ€” identical result, rarely needed

Genuinely similar to Phase 8 Module 7's auto โ€” the compiler looks at the actual arguments you pass and deduces T automatically. You almost never need to write getMax<int>(...) explicitly; it's available when genuinely needed, but the implicit version is the normal, everyday style.

โš ๏ธ A Genuine Trap โ€” Mismatched Types
C++ โ€” this won't compile
getMax(5, 3.5);   // COMPILER ERROR โ€” T can't be BOTH int and double at once
โš ๏ธ
A single T means both arguments must genuinely be the same typeOur simple template <typename T> T getMax(T a, T b) requires a and b to both deduce to the exact same T โ€” mixing int and double gives the compiler no single consistent type to use, and it refuses to guess. Module 5 covers how to genuinely support different types for different parameters, when that's actually what you need.
๐Ÿง  Quick Check
Why does calling getMax(5, 3.5) fail to compile, given template <typename T> T getMax(T a, T b)?
MODULE 05

๐Ÿ‘ฅ Multiple Template Parameters

The fix for last module's trap โ€” genuinely supporting different types for different parameters, when that's actually what you need.

๐Ÿ‘ฅ More Than One Placeholder Type
C++
template <typename T1, typename T2>
void printPair(T1 first, T2 second) {
    std::cout << first << " and " << second << std::endl;
}

printPair(42, "apples");   // T1 = int, T2 = const char* โ€” genuinely different types, no problem

You can declare as many independent placeholder types as you genuinely need, separated by commas. Each parameter's type is deduced independently โ€” this directly fixes Module 4's mismatched-type trap, since T1 and T2 are no longer forced to match.

๐Ÿ”— This Is Exactly Phase 7's std::pair and std::map
C++ โ€” conceptually, roughly how pair is built
template <typename T1, typename T2>
class Pair {
public:
    T1 first;
    T2 second;
};

std::pair<std::string, int> from Phase 7 Module 9 is precisely this pattern โ€” two independent template parameters, letting the key and value hold genuinely different types. std::map<K, V> follows the exact same idea underneath.

๐Ÿง  Quick Check
Why does template <typename T1, typename T2> genuinely fix Module 4's mismatched-type problem?
MODULE 06

๐ŸŽฏ Template Specialization

Sometimes the generic, one-size-fits-all template logic genuinely isn't right for one specific type. Specialization lets you carve out an exception.

๐Ÿค” When the Generic Version Isn't Quite Right

Suppose a generic describe(T value) template prints a value directly โ€” that works fine for numbers, but for a bool, printing raw 1 or 0 is far less readable than printing "true" or "false". Template specialization lets you write a genuinely different implementation specifically for one particular type, while every OTHER type still uses the generic version.

๐ŸŽฏ Providing a Specific Override
C++
template <typename T>
void describe(T value) {
    std::cout << "Value: " << value << std::endl;
}

template <>   // empty <> means this is a full specialization
void describe<bool>(bool value) {
    std::cout << "Value: " << (value ? "true" : "false") << std::endl;
}

describe(42);     // uses the generic version โ€” "Value: 42"
describe(true);   // uses the bool specialization โ€” "Value: true"

The compiler always prefers a specialization that exactly matches the type being used โ€” describe(true) genuinely calls the bool version, while everything else falls through to the generic template.

๐Ÿง  Quick Check
What is template specialization used for?
MODULE 07

๐Ÿ”— Templates and the STL

A direct look back at Phase 7 with this phase's new understanding โ€” connecting everything you've learned about templates to syntax you've already used for weeks.

๐Ÿ”— Every STL Container Is a Class Template
Phase 7 SyntaxWhat's Genuinely Happening
std::vector<int>The class template vector, instantiated with T = int
std::map<std::string, int>The class template map, instantiated with Key = std::string, Value = int โ€” Module 5's multiple parameters
std::pair<std::string, int>Genuinely, structurally almost identical to Module 5's own Pair example
๐Ÿ”— Every STL Algorithm Is a Function Template
C++ โ€” conceptually, roughly how std::sort is templated
template <typename Iterator>
void sort(Iterator begin, Iterator end) {
    // sorting logic that works for ANY container's iterators
}

This is exactly why Phase 7 Module 7's std::sort worked identically on a vector<int> and could equally work on a vector<std::string> or your own Box<T> objects (if they supported comparison) โ€” one function template, genuinely reused across countless different types.

๐Ÿ’ก
This is genuinely the point of the whole phaseYou've now seen the actual mechanism behind syntax you've been using since Phase 7 without fully understanding it. Nothing about the STL is magic โ€” it's the same template <typename T> pattern from Module 1, applied at a genuinely large, professional scale.
๐Ÿง  Quick Check
Why does std::sort work identically on a vector<int> and a vector<std::string>?
MODULE 08 ๐Ÿ

๐Ÿ Capstone โ€” A Generic Stack

Every idea from this phase, combined: a genuinely reusable class template, built on Phase 7's vector and Phase 8's move semantics, working identically for any type.

๐ŸŽฏ The Goal

Build a Stack<T> class template โ€” last-in-first-out storage โ€” with push, pop, and top operations, that works identically whether T is int, std::string, or a custom class, without writing it more than once.

๐Ÿ’ป A Reference Solution
main.cpp
#include <iostream>
#include <vector>
#include <stdexcept>

template <typename T>
class Stack {
private:
    std::vector<T> items;
public:
    void push(T value) {
        items.push_back(value);
    }

    void pop() {
        if (items.empty()) {
            throw std::runtime_error("Cannot pop from an empty stack");
        }
        items.pop_back();
    }

    T& top() {
        if (items.empty()) {
            throw std::runtime_error("Cannot access top of an empty stack");
        }
        return items.back();
    }

    bool empty() {
        return items.empty();
    }
};

int main() {
    Stack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    intStack.push(3);
    std::cout << "Top: " << intStack.top() << std::endl;   // 3
    intStack.pop();
    std::cout << "Top after pop: " << intStack.top() << std::endl;   // 2

    Stack<std::string> strStack;   // the SAME class template, a completely different type
    strStack.push("hello");
    std::cout << "String stack top: " << strStack.top() << std::endl;

    return 0;
}

(throw std::runtime_error(...) is a preview of Phase 10's exception handling โ€” for now, just recognize it as "signal that something went genuinely wrong.")

๐Ÿ” Why It's Built This Way
  • std::vector<T> internally handles the actual storage โ€” Module 6's insight that STL containers are themselves templates, reused here directly rather than reinventing dynamic storage.
  • Both pop() and top() check empty() first โ€” Phase 5 Module 6's encapsulation/validation principle, protecting the class from an invalid operation.
  • Stack<int> and Stack<std::string> are genuinely separate types (Module 3), yet came from writing this class exactly once.
๐Ÿ Extend It Yourself

Add a size() Method and a Max-Capacity Check

Add a size() method returning the current element count, and modify the constructor to accept an optional maximum capacity โ€” push() should throw if the stack is already full. This combines Phase 5's constructor defaults (Module 7) with this phase's template class, genuinely testing whether you can integrate ideas across multiple phases, not just repeat this lesson's exact pattern.

Roadmap