Module 07: C++ Templates
Key Concepts:
- Function templates
- Class templates
- Template instantiation
- Template specialization
1. Why Templates?
Section titled “1. Why Templates?”The Problem: Code Duplication
Section titled “The Problem: Code Duplication”// Without templates - must write for each typeint maxInt(int a, int b) { return (a > b) ? a : b; }double maxDouble(double a, double b) { return (a > b) ? a : b; }std::string maxString(std::string a, std::string b) { return (a > b) ? a : b; }The Solution: Templates
Section titled “The Solution: Templates”// With templates - one definition, works for all typestemplate <typename T>T max(T a, T b) { return (a > b) ? a : b;}
// Usagemax(3, 5); // Instantiates max<int>max(3.14, 2.71); // Instantiates max<double>max(str1, str2); // Instantiates max<std::string>2. Function Templates
Section titled “2. Function Templates”Basic Syntax
Section titled “Basic Syntax”template <typename T>T functionName(T param1, T param2) { // T can be used like any type return param1 + param2;}Multiple Type Parameters
Section titled “Multiple Type Parameters”template <typename T, typename U>T convert(U value) { return static_cast<T>(value);}
// Usageint i = convert<int>(3.14); // Explicit T, deduced UTemplate vs typename
Section titled “Template vs typename”template <typename T> // Modern styletemplate <class T> // Also valid, means the same thing3. Module 07 Exercise 00: swap, min, max
Section titled “3. Module 07 Exercise 00: swap, min, max”// swap: Exchange values of two variablestemplate <typename T>void swap(T& a, T& b) { T temp = a; a = b; b = temp;}
// min: Return smaller value (return second if equal)template <typename T>T const& min(T const& a, T const& b) { return (a < b) ? a : b;}
// max: Return larger value (return second if equal)template <typename T>T const& max(T const& a, T const& b) { return (a > b) ? a : b;}Test Code
Section titled “Test Code”int main() { int a = 2; int b = 3;
::swap(a, b); std::cout << "a = " << a << ", b = " << b << std::endl; std::cout << "min(a, b) = " << ::min(a, b) << std::endl; std::cout << "max(a, b) = " << ::max(a, b) << std::endl;
std::string c = "chaine1"; std::string d = "chaine2";
::swap(c, d); std::cout << "c = " << c << ", d = " << d << std::endl; std::cout << "min(c, d) = " << ::min(c, d) << std::endl; std::cout << "max(c, d) = " << ::max(c, d) << std::endl;
return 0;}4. Module 07 Exercise 01: iter
Section titled “4. Module 07 Exercise 01: iter”Function Pointers as Template Parameters
Section titled “Function Pointers as Template Parameters”// iter: Apply function to each element of array// F can be a function pointer OR a functor typetemplate <typename T, typename F>void iter(T* array, size_t length, F func) { for (size_t i = 0; i < length; i++) { func(array[i]); }}Explicit Function Pointer Version
Section titled “Explicit Function Pointer Version”// More explicit: F is specifically a function pointertemplate <typename T>void iter(T* array, size_t length, void (*func)(T&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}Template Overloading: const vs non-const
Section titled “Template Overloading: const vs non-const”The same template can be overloaded for const and non-const operations:
// Non-const version - can modify elementstemplate <typename T>void iter(T* array, size_t length, void (*func)(T&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}
// Const version - read-only operationstemplate <typename T>void iter(T* array, size_t length, void (*func)(T const&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}Why Both Versions?
Section titled “Why Both Versions?”// Function that modifies (needs non-const reference)template <typename T>void increment(T& elem) { elem++;}
// Function that only reads (uses const reference)template <typename T>void print(T const& elem) { std::cout << elem << std::endl;}
int arr[] = {1, 2, 3};
// Calls non-const versioniter(arr, 3, increment<int>); // arr is now {2, 3, 4}
// Calls const versioniter(arr, 3, print<int>); // Prints elementsFunction Pointer Syntax with Templates
Section titled “Function Pointer Syntax with Templates”// When calling with a template function, you must instantiate it:iter(arr, 3, print<int>); // Explicit template argumentiter(arr, 3, &print<int>); // & is optional
// With non-template functions:void printInt(int const& x) { std::cout << x; }iter(arr, 3, printInt); // No <> neededTest Code
Section titled “Test Code”template <typename T>void print(T const& elem) { std::cout << elem << std::endl;}
template <typename T>void increment(T& elem) { elem++;}
int main() { int arr[] = {1, 2, 3, 4, 5};
std::cout << "Original:" << std::endl; iter(arr, 5, print<int>);
iter(arr, 5, increment<int>);
std::cout << "After increment:" << std::endl; iter(arr, 5, print<int>);
return 0;}5. Class Templates
Section titled “5. Class Templates”Basic Syntax
Section titled “Basic Syntax”template <typename T>class Container {private: T* _data; size_t _size;
public: Container(size_t size); Container(const Container& other); Container& operator=(const Container& other); ~Container();
T& operator[](size_t index); const T& operator[](size_t index) const; size_t size() const;};Implementation
Section titled “Implementation”// For class templates, implementation MUST be in header// Or in a .tpp file included by the header
template <typename T>Container<T>::Container(size_t size) : _size(size) { _data = new T[size](); // () for value initialization}
template <typename T>Container<T>::~Container() { delete[] _data;}
template <typename T>T& Container<T>::operator[](size_t index) { if (index >= _size) throw std::out_of_range("Index out of bounds"); return _data[index];}6. Module 07 Exercise 02: Array
Section titled “6. Module 07 Exercise 02: Array”template <typename T>class Array {private: T* _array; unsigned int _size;
public: // Default constructor - empty array Array() : _array(NULL), _size(0) {}
// Size constructor - array of n elements Array(unsigned int n) : _array(new T[n]()), _size(n) {}
// Copy constructor - deep copy Array(const Array& other) : _array(NULL), _size(0) { *this = other; }
// Assignment operator - deep copy Array& operator=(const Array& other) { if (this != &other) { delete[] _array; _size = other._size; _array = new T[_size]; for (unsigned int i = 0; i < _size; i++) _array[i] = other._array[i]; } return *this; }
// Destructor ~Array() { delete[] _array; }
// Element access with bounds checking T& operator[](unsigned int index) { if (index >= _size) throw std::out_of_range("Index out of bounds"); return _array[index]; }
const T& operator[](unsigned int index) const { if (index >= _size) throw std::out_of_range("Index out of bounds"); return _array[index]; }
// Size getter unsigned int size() const { return _size; }};std::out_of_range Exception
Section titled “std::out_of_range Exception”#include <stdexcept> // for std::out_of_range
// out_of_range is used for index/bounds errorsT& operator[](unsigned int index) { if (index >= _size) throw std::out_of_range("Index out of bounds"); return _array[index];}
// Usage:try { Array<int> arr(5); arr[10] = 42; // Throws out_of_range}catch (std::out_of_range& e) { std::cout << "Exception: " << e.what() << std::endl;}catch (std::exception& e) { // Catches any standard exception std::cout << "Error: " << e.what() << std::endl;}Exception Hierarchy for Reference
Section titled “Exception Hierarchy for Reference”std::exception├── std::logic_error│ ├── std::out_of_range <- Use for index errors│ ├── std::invalid_argument <- Use for bad function arguments│ └── std::length_error <- Use for size limit errors└── std::runtime_error <- Use for general runtime errorsTest Code
Section titled “Test Code”int main() { // Test default constructor Array<int> empty; std::cout << "Empty size: " << empty.size() << std::endl;
// Test size constructor Array<int> arr(5); std::cout << "Size: " << arr.size() << std::endl;
// Test element access for (unsigned int i = 0; i < arr.size(); i++) arr[i] = i * 2;
// Test copy Array<int> copy(arr); arr[0] = 100; std::cout << "arr[0]: " << arr[0] << std::endl; std::cout << "copy[0]: " << copy[0] << std::endl; // Should be 0
// Test bounds checking try { arr[100] = 42; } catch (std::exception& e) { std::cout << "Exception: " << e.what() << std::endl; }
return 0;}7. Template Instantiation
Section titled “7. Template Instantiation”Implicit Instantiation
Section titled “Implicit Instantiation”// Compiler generates code when template is usedArray<int> intArray; // Generates Array<int>Array<double> dblArray; // Generates Array<double>Explicit Instantiation
Section titled “Explicit Instantiation”// Force generation of specific types (rarely needed)template class Array<int>;template void swap<double>(double&, double&);8. Where to Put Template Code
Section titled “8. Where to Put Template Code”Option 1: All in Header (.hpp)
Section titled “Option 1: All in Header (.hpp)”template <typename T>class Array { // Declaration AND implementation here};Option 2: Separate .tpp File
Section titled “Option 2: Separate .tpp File”template <typename T>class Array { // Declaration only};
#include "Array.tpp" // Include implementation at end
// Array.tpptemplate <typename T>Array<T>::Array() { /* ... */ }Why Not .cpp?
Section titled “Why Not .cpp?”Templates need to be visible at instantiation point. If implementation is in .cpp, the compiler can’t generate the code.
9. Common Template Patterns
Section titled “9. Common Template Patterns”Type Traits (C++98 style)
Section titled “Type Traits (C++98 style)”template <typename T>struct is_pointer { static const bool value = false;};
template <typename T>struct is_pointer<T*> { static const bool value = true;};Default Template Parameters
Section titled “Default Template Parameters”template <typename T = int>class Stack { // Default to int if no type specified};
Stack<> intStack; // Uses default: intStack<double> dblStack; // Explicit: doubleQuick Reference
Section titled “Quick Reference”Function Template
Section titled “Function Template”template <typename T>T functionName(T param) { /* ... */ }Class Template
Section titled “Class Template”template <typename T>class ClassName { // Members using T};
// Outside class definitiontemplate <typename T>ClassName<T>::methodName() { /* ... */ }Key Rules
Section titled “Key Rules”- Templates must be in headers (or included .tpp)
- Use
typenameorclass(interchangeable) - Types must support operations used in template
- Deep copy for pointer members in class templates