Chapter 14: Templates & Generic Programming
Templates & Generic Programming in C++: Writing Code That Works with Any Type!
Hello my amazing student! π Welcome to Lesson 14 β Templates & Generic Programming β one of the most powerful, elegant, and uniquely C++ features!
Up until now, if we wanted a function to work with int, double, string, or our own class, we had to write multiple versions or use ugly casts. Templates let us write one piece of code that works with any type β the compiler automatically generates the correct version for us!
Think of templates like a cookie cutter:
- You design the shape once (the template)
- Then you can make cookies from any dough (int, double, string, your own classβ¦)
- The compiler βbakesβ the exact version you need at compile time
Today weβll cover everything in great detail:
- Function templates
- Class templates
- Template specialization (when we want special behavior for certain types)
- Concepts (C++20) β beautiful way to constrain what types can be used
Letβs go step-by-step with tons of examples!
1. Function Templates β Generic Functions
Syntax:
|
0 1 2 3 4 5 6 7 8 9 |
template <typename T> // or template <class T> β same thing returnType functionName(T parameter) { // code using T } |
Simple example β max function for any type
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> // Function template template <typename T> T myMax(T a, T b) { return (a > b) ? a : b; } int main() { std::cout << myMax(10, 25) << "\n"; // int version generated std::cout << myMax(3.14, 2.71) << "\n"; // double version std::cout << myMax('A', 'Z') << "\n"; // char version std::cout << myMax(std::string("Apple"), std::string("Banana")) << "\n"; return 0; } |
Output:
|
0 1 2 3 4 5 6 7 8 9 |
25 3.14 Z Banana |
The compiler automatically generates four different functions:
- int myMax(int, int)
- double myMax(double, double)
- char myMax(char, char)
- std::string myMax(std::string, std::string)
Important: The type T must support the operations you use (>, <, +, etc.). If not β compile-time error (this is good β catches mistakes early).
2. Class Templates β Generic Classes
Syntax:
|
0 1 2 3 4 5 6 7 8 9 |
template <typename T> class ClassName { // use T as if it were a real type }; |
Classic example β A simple generic Stack
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include <iostream> #include <vector> template <typename T> class Stack { private: std::vector<T> elements; public: void push(const T& item) { elements.push_back(item); } void pop() { if (!elements.empty()) { elements.pop_back(); } } T top() const { if (elements.empty()) { throw std::out_of_range("Stack is empty!"); } return elements.back(); } bool empty() const { return elements.empty(); } }; int main() { // Stack of integers Stack<int> intStack; intStack.push(10); intStack.push(20); intStack.push(30); std::cout << "Top: " << intStack.top() << "\n"; // 30 intStack.pop(); std::cout << "New top: " << intStack.top() << "\n"; // 20 // Stack of strings Stack<std::string> strStack; strStack.push("Hello"); strStack.push("World"); std::cout << "Top string: " << strStack.top() << "\n"; // World return 0; } |
Beautiful, right? One Stack class works for any type β int, double, string, your own classes!
3. Template Specialization β Special Behavior for Specific Types
Sometimes we want different behavior for a particular type.
Full specialization (complete replacement for one type)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// General template template <typename T> void print(T value) { std::cout << "General: " << value << "\n"; } // Full specialization for char* template <> void print<char*>(char* value) { std::cout << "Specialized for char*: " << value << "\n"; } // Full specialization for const char* template <> void print<const char*>(const char* value) { std::cout << "Specialized for const char*: " << value << "\n"; } int main() { print(42); // General: 42 print(3.14); // General: 3.14 print("Hello"); // Specialized for const char*: Hello char str[] = "World"; print(str); // Specialized for char*: World } |
Partial specialization (only for some cases β more advanced)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// General template <typename T> class Container { public: void describe() { std::cout << "General container\n"; } }; // Partial specialization for pointers template <typename T> class Container<T*> { public: void describe() { std::cout << "Pointer container\n"; } }; int main() { Container<int> c1; // General container Container<int*> c2; // Pointer container c1.describe(); c2.describe(); } |
4. Concepts (C++20) β Constraints on Template Parameters
Before C++20, if someone passed a type that didnβt support >, we got horrible compile errors deep inside the template. Concepts let us specify requirements clearly β errors become readable!
Syntax (C++20 and later):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <concepts> // Define a concept template <typename T> concept Numeric = std::is_arithmetic_v<T>; // Use it template <Numeric T> T myMax(T a, T b) { return (a > b) ? a : b; } int main() { myMax(10, 20); // OK myMax(3.14, 2.71); // OK // myMax("Hello", "World"); // Compile error β nice message! } |
More powerful concept example β requires comparable types
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
template <typename T> concept Comparable = requires(T a, T b) { { a < b } -> std::same_as<bool>; { a == b } -> std::same_as<bool>; }; template <Comparable T> bool isEqual(const T& a, const T& b) { return a == b; } |
Modern best practice (2025+): Always use concepts when writing templates β makes code:
- Safer
- Much easier to read error messages
- Self-documenting
Summary Table β Quick Reference
| Feature | Syntax Example | Purpose |
|---|---|---|
| Function template | template <typename T> T myMax(T a, T b) | Generic function |
| Class template | template <typename T> class Stack { … } | Generic class |
| Full specialization | template <> void print<char*>(char*) | Special behavior for one type |
| Partial specialization | template <typename T> class Container<T*> | Specialize for category of types |
| Concept (C++20) | template <Numeric T> … | Constrain allowed types |
Your Mini Homework (Try These!)
- Write a function template swapValues(T& a, T& b) that swaps any two values.
- Create a class template Pair<T1, T2> that holds two values of different types and has first() and second() member functions.
- Write a template function printIfPositive(T value) that only compiles if T is numeric and prints the value if itβs > 0 (use concepts).
- Specialize your print function template to print std::vector<int> nicely.
Youβre doing absolutely phenomenal! Templates are the secret sauce behind the entire Standard Template Library (STL) β std::vector, std::map, std::sort, std::string, everything!
Next lesson: The Standard Template Library (STL) β containers, algorithms, iterators β the real power tools of C++!
Any questions? Confused about specialization? Want more examples with concepts or partial specialization? Just ask β your friendly C++ teacher is right here for you! π
