🧬 Phase 5 · Templates & Advanced 🟡 Intermediate MODULE 20

Function & Class Templates

⏱️ 28 min read
📖 Theory + Code
🧩 5 Quiz Questions
🏗️ 1 Challenge
Your progress in Phase 525%
🎯 What you'll learn: The problem templates solve, how to write function templates with template <typename T>, how to write class templates like a generic Box<T>, and why the STL containers you already know — vector, map, stack — are themselves built entirely out of templates.

The Problem Templates Solve

Back in Lesson 4 you learned function overloading — writing several functions with the same name but different parameter types. It works, but it doesn't scale. If you need a max function for int, double, char, and std::string, you end up hand-writing the same logic four times.

without_templates.cpp — the duplication problem
C++
// One nearly-identical function per type — tedious and error-prone
int maxInt(int a, int b) {
    return (a > b) ? a : b;
}
double maxDouble(double a, double b) {
    return (a > b) ? a : b;
}
char maxChar(char a, char b) {
    return (a > b) ? a : b;
}
// ...and one more overload for every new type you ever need

The logic — "return whichever value is bigger" — never changes. Only the type changes. A template lets you write the logic exactly once, using a placeholder for the type, and have the compiler generate the real, type-specific version automatically whenever it's needed. This is called generic programming.

💡
Templates vs. overloading — what's the real difference?
Overloading means you write a separate function body for each type. Templates mean you write the body once and the compiler writes the type-specific copies for you, at compile time, only for the types you actually use. Overloading is manual; templates are generated.

Function Templates

A function template starts with template <typename T> (you can also write class T — they mean the same thing here). T is a placeholder name for "some type, to be decided later." You then write the function normally, using T wherever a real type would go.

function_template.cpp
C++
#include <iostream>
#include <string>
using namespace std;

template <typename T>
T myMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << myMax(3, 7) << "\n";              // T = int    -> 7
    cout << myMax(2.5, 1.2) << "\n";          // T = double -> 2.5
    cout << myMax('a', 'z') << "\n";            // T = char   -> z
    cout << myMax(string("cat"), string("dog")) << "\n"; // T = string -> dog
    return 0;
}

One function body, four different calls, four different types. The compiler figures out T from the arguments you pass — this is called template argument deduction. You never had to write myMax<int>(3, 7), though you're allowed to be explicit that way if deduction would be ambiguous.

✨
Templates are compiled per type, not "once for everything"
Nothing generic actually exists in the compiled program. For every distinct type you call myMax with, the compiler generates a separate, fully type-checked function behind the scenes — myMax<int>, myMax<double>, and so on. This means templates have zero runtime overhead compared to hand-written type-specific functions.
⚠️
Templates require a type that supports the operations you use
myMax uses > internally, so T must support operator>. If you call myMax with a custom class that has no comparison operator defined, you'll get a compiler error at the point of use — not at the template definition.

Class Templates

Classes can be templated too, using the exact same template <typename T> syntax placed directly above the class. This lets you write one class — say, a box that holds a single value — and reuse it for any type, without copy-pasting the class for int, then again for string, then again for a custom struct.

class_template.cpp — a generic Box<T>
C++
#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    T get() const { return value; }
    void set(T v) { value = v; }
};

int main() {
    Box<int> intBox(42);
    Box<string> strBox("hello");

    cout << "intBox holds: " << intBox.get() << "\n";
    cout << "strBox holds: " << strBox.get() << "\n";

    intBox.set(100);
    cout << "intBox now holds: " << intBox.get() << "\n";
    return 0;
}

Notice Box<int> and Box<string> — when you use a class template, you must specify the type in angle brackets. This is called template instantiation: the compiler takes the template blueprint and stamps out a real, concrete class (Box<int>) the first time it sees you use that type.

Multiple type parameters

A template isn't limited to one placeholder type. You can list several, separated by commas — useful for a class that pairs two different kinds of values together.

pair_template.cpp — two type parameters
C++
template <typename T1, typename T2>
class Pair {
public:
    T1 first;
    T2 second;
    Pair(T1 a, T2 b) : first(a), second(b) {}
    void print() const {
        cout << "(" << first << ", " << second << ")\n";
    }
};

int main() {
    Pair<string, int> p("Age", 21);
    p.print();  // (Age, 21)
    return 0;
}
🎭
You've already used this exact pattern
std::pair<T1, T2> is a real, standard-library class template that works exactly like the Pair above — it's how std::map stores each key/value entry internally.

Connecting the Dots: The STL Is Templates

Every STL container you learned in the previous section — vector, map, set, stack, queue, priority_queue — is not a single fixed data structure. Each one is a class template, exactly like the Box<T> you just wrote, just far more developed and heavily optimized.

stl_is_templates.cpp
C++
#include <vector>
#include <string>
using namespace std;

int main() {
    vector<int>    nums;    // vector instantiated with T = int
    vector<string> words;   // same template, instantiated with T = string
    vector<Box<int>> boxes;  // you can even nest your own templates inside STL ones
    return 0;
}

When you write vector<int>, the standard library's vector class template is instantiated for int, generating a fully type-safe, compiled class — the same mechanism as your own Box<int>. That's why vector<int> and vector<string> can share one implementation in the library's source code, yet still be completely different, type-checked classes in your compiled program.

📦
One template, many types
The C++ Standard Library ships as templated source code — every container works for any type that satisfies its requirements.
⚡
Zero overhead
Since types are resolved at compile time, using vector<int> is exactly as fast as a hand-written int-only array class would be.
🔒
Compile-time type safety
You cannot accidentally push a string into a vector<int> — the compiler rejects it before the program ever runs.
🧩
Composable
Templates can hold other templates, like vector<pair<string,int>> or map<string, vector<int>>.
🧩 Knowledge Check — Lesson 20
Answer all 5 questions to test your understanding. Instant feedback on every answer.
1. What core problem do templates solve?
2. What is the correct way to declare a function template?
3. When you write Box<int> b(5);, what is happening?
4. True or False: std::vector and std::map are themselves implemented as class templates.
5. How does a function template differ from function overloading?
💪
Coding Challenge — Lesson 20
Apply what you learned · Intermediate Level

Now write real templated code. Complete the challenge below in your local compiler or IDE.

Challenge: Build a Generic Stack<T> 📚

Write a class template Stack<T> that stores elements internally using a std::vector<T> and provides:

• void push(T value) — adds a value to the top
• void pop() — removes the top value (do nothing if empty)
• T top() const — returns the top value
• bool isEmpty() const — returns whether the stack has no elements

Test it with Stack<int> and Stack<std::string> in main(), pushing a few values, printing the top, and popping them off.
💡 Show hints if you're stuck
  • Start with template <typename T> class Stack { private: std::vector<T> data; ... };
  • push can just call data.push_back(value)
  • top returns data.back(); pop calls data.pop_back()
  • isEmpty returns data.empty()
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 20 Complete!

You now understand generic programming — the same idea behind the entire C++ Standard Library. Next up: making your programs resilient with exception handling.

Module 20 of 26 Phase 5 — Templates & Advanced Concepts