🧬 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