Function & Class Templates
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.
// 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.
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.
#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.
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.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.
#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.
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; }
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.
#include <vector> #include <string> using namespace std; int main() { vector<int> nums; // vectorinstantiated 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.
vector<int> is exactly as fast as a hand-written int-only array class would be.string into a vector<int> — the compiler rejects it before the program ever runs.vector<pair<string,int>> or map<string, vector<int>>.Box<int> b(5);, what is happening?std::vector and std::map are themselves implemented as class templates.Now write real templated code. Complete the challenge below in your local compiler or IDE.
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 elementsTest 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; ... }; pushcan just calldata.push_back(value)topreturnsdata.back();popcallsdata.pop_back()isEmptyreturnsdata.empty()