Module 07 Tutorial
Prerequisites: Review Module 07 Concepts first.
This module introduces C++ templates - the foundation of generic programming. You’ll learn to write code that works with any type through function templates and class templates.
Exercise 00: Start with a Few Functions
Section titled “Exercise 00: Start with a Few Functions”Subject Analysis
Section titled “Subject Analysis”Implement three function templates:
swap(a, b)- exchange two valuesmin(a, b)- return the smaller valuemax(a, b)- return the larger value
Key constraints:
- Must work with any comparable type
- When values are equal, return the second argument
- Must work with the provided test main
Test main provided:
int main() { int a = 2, 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", 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;}Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What is a template?
- A blueprint for creating functions/classes
- Compiler generates actual code when you use it
template <typename T>declares a type parameter
-
Why use
::swapinstead ofswap?::means global namespace- Avoids conflict with
std::swap - Your function must be in global scope
-
Why return reference for min/max?
- Efficiency (avoid copying)
- Subject requirement for equal case
- Returning reference to second argument when equal
-
What operations must T support?
swap: assignment (=)min: less-than comparison (<)max: greater-than comparison (>)
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Basic swap template
#ifndef WHATEVER_HPP#define WHATEVER_HPP
template <typename T>void swap(T& a, T& b) { T temp = a; // T must support copy construction a = b; // T must support assignment b = temp;}
#endifStage 2: Add min with correct equal behavior
template <typename T>T const& min(T const& a, T const& b) { // When equal, return b (second argument) return (a < b) ? a : b;}Stage 3: Add max with correct equal behavior
template <typename T>T const& max(T const& a, T const& b) { // When equal, return b (second argument) return (a > b) ? a : b;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Template syntax breakdown:
| Syntax | Meaning |
|---|---|
template <typename T> | Declares T as a placeholder type |
T& a | Reference to T (modifiable) |
T const& a | Const reference to T (read-only) |
T const& min(...) | Returns const reference to T |
Why const references?
// By value - BAD: copies both argumentsT min(T a, T b) { return (a < b) ? a : b; }
// By const reference - GOOD: no copiesT const& min(T const& a, T const& b) { return (a < b) ? a : b; }The equal case:
// When a == b, (a < b) is false, so return breturn (a < b) ? a : b; // Returns b when equal
// When a == b, (a > b) is false, so return breturn (a > b) ? a : b; // Returns b when equalCommon Pitfalls
Section titled “Common Pitfalls”1. Return by value instead of reference:
// WRONG: Copies and loses "return second when equal" behaviortemplate <typename T>T min(T a, T b) { return (a < b) ? a : b; }
// RIGHT: Returns reference to actual argumenttemplate <typename T>T const& min(T const& a, T const& b) { return (a < b) ? a : b; }2. Wrong condition for equal case:
// WRONG: Returns first when equalreturn (a <= b) ? a : b;
// RIGHT: Returns second when equalreturn (a < b) ? a : b;3. Not in global namespace:
// WRONG: In namespace, ::swap won't find itnamespace MyLib { template <typename T> void swap(T& a, T& b) { ... }}
// RIGHT: In global scopetemplate <typename T>void swap(T& a, T& b) { ... }4. Using wrong parameter types for swap:
// WRONG: By value - swaps copies, originals unchangedtemplate <typename T>void swap(T a, T b) { T temp = a; a = b; b = temp;}
// RIGHT: By reference - modifies original valuestemplate <typename T>void swap(T& a, T& b) { T temp = a; a = b; b = temp;}Testing Tips
Section titled “Testing Tips”#include <iostream>#include <string>#include "whatever.hpp"
int main() { // Test with int int a = 2, b = 3; std::cout << "Before: a=" << a << ", b=" << b << std::endl; ::swap(a, b); std::cout << "After swap: a=" << a << ", b=" << b << std::endl; std::cout << "min: " << ::min(a, b) << std::endl; std::cout << "max: " << ::max(a, b) << std::endl;
// Test with string std::string c = "chaine1", d = "chaine2"; ::swap(c, d); std::cout << "c=" << c << ", d=" << d << std::endl; std::cout << "min: " << ::min(c, d) << std::endl; std::cout << "max: " << ::max(c, d) << std::endl;
// Test equal case - should return second int x = 5, y = 5; std::cout << "Equal test: &x=" << &x << ", &y=" << &y << std::endl; std::cout << "min returns: " << &(::min(x, y)) << std::endl; std::cout << "max returns: " << &(::max(x, y)) << std::endl; // Should print &y for both!
return 0;}Expected output:
Before: a=2, b=3After swap: a=3, b=2min: 2max: 3c=chaine2, d=chaine1min: chaine1max: chaine2Equal test: &x=0x7fff..., &y=0x7fff...min returns: 0x7fff... (same as &y)max returns: 0x7fff... (same as &y)Final Code
Section titled “Final Code”#ifndef WHATEVER_HPP#define WHATEVER_HPP
template <typename T>void swap(T& a, T& b) { T temp = a; a = b; b = temp;}
template <typename T>T const& min(T const& a, T const& b) { return (a < b) ? a : b;}
template <typename T>T const& max(T const& a, T const& b) { return (a > b) ? a : b;}
#endifExercise 01: Iter
Section titled “Exercise 01: Iter”Subject Analysis
Section titled “Subject Analysis”Implement a function template iter that:
- Takes an array address
- Takes the array length
- Takes a function to apply to each element
- Calls the function on every element
Key insight: This is a generic algorithm - it works with any array type and any function.
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What are the parameters?
T* array- pointer to first elementsize_t length- number of elementsvoid (*func)(T&)- function pointer that takes element reference
-
Do we need const version?
- Yes! For read-only operations (like printing)
void (*func)(T const&)for const access
-
How to call iter with a template function?
- Must explicitly specify type:
iter(arr, 5, print<int>) - Compiler can’t deduce template parameter from function pointer
- Must explicitly specify type:
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Basic iter (modifying)
#ifndef ITER_HPP#define ITER_HPP
#include <cstddef> // size_t
template <typename T>void iter(T* array, size_t length, void (*func)(T&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}
#endifStage 2: Add const version (reading)
template <typename T>void iter(T* array, size_t length, void (*func)(T const&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}Stage 3: Test functions
#include <iostream>#include "iter.hpp"
// Print function (const - doesn't modify)template <typename T>void print(T const& elem) { std::cout << elem << " ";}
// Double function (modifies)template <typename T>void doubleValue(T& elem) { elem *= 2;}
int main() { int arr[] = {1, 2, 3, 4, 5};
std::cout << "Original: "; iter(arr, 5, print<int>); std::cout << std::endl;
iter(arr, 5, doubleValue<int>);
std::cout << "Doubled: "; iter(arr, 5, print<int>); std::cout << std::endl;
return 0;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Function pointer parameter:
| Syntax | Meaning |
|---|---|
void (*func)(T&) | Pointer to function taking T& and returning void |
void (*func)(T const&) | Pointer to function taking T const& and returning void |
func(array[i]) | Call the function with element as argument |
Why two overloads?
// Modifying version - when function changes elementsvoid increment(int& n) { n++; }iter(arr, 5, increment); // Uses void (*func)(T&)
// Const version - when function only readsvoid print(int const& n) { std::cout << n; }iter(arr, 5, print); // Uses void (*func)(T const&)Template function as argument:
// Template functiontemplate <typename T>void print(T const& elem) { std::cout << elem; }
// WRONG: Can't deduce Titer(arr, 5, print); // Error!
// RIGHT: Explicit typeiter(arr, 5, print<int>); // OKCommon Pitfalls
Section titled “Common Pitfalls”1. Missing const overload:
// WRONG: Only non-const versiontemplate <typename T>void iter(T* array, size_t length, void (*func)(T&)) { ... }
// This fails:void print(int const& n) { std::cout << n; }iter(arr, 5, print); // Error! Can't convert function types
// RIGHT: Add const overloadtemplate <typename T>void iter(T* array, size_t length, void (*func)(T const&)) { ... }2. Forgetting type specification:
template <typename T>void print(T const& elem) { std::cout << elem; }
// WRONG: Compiler can't deduceiter(arr, 5, print);
// RIGHT: Specify typeiter(arr, 5, print<int>);3. Using wrong size type:
// WRONG: int for size (can be negative)void iter(T* array, int length, ...) { ... }
// RIGHT: size_t (unsigned, correct type for sizes)void iter(T* array, size_t length, ...) { ... }Testing Tips
Section titled “Testing Tips”#include <iostream>#include <string>#include "iter.hpp"
template <typename T>void print(T const& elem) { std::cout << "[" << elem << "] ";}
template <typename T>void increment(T& elem) { elem++;}
int main() { // Test with int array int numbers[] = {1, 2, 3, 4, 5}; std::cout << "Int array: "; iter(numbers, 5, print<int>); std::cout << std::endl;
iter(numbers, 5, increment<int>); std::cout << "After increment: "; iter(numbers, 5, print<int>); std::cout << std::endl;
// Test with string array std::string words[] = {"hello", "world", "test"}; std::cout << "String array: "; iter(words, 3, print<std::string>); std::cout << std::endl;
// Test with empty array int empty[1] = {0}; iter(empty, 0, print<int>); // Should do nothing std::cout << "Empty test passed" << std::endl;
return 0;}Test bash script:
c++ -Wall -Wextra -Werror -std=c++98 main.cpp -o iter./iterFinal Code
Section titled “Final Code”#ifndef ITER_HPP#define ITER_HPP
#include <cstddef>
template <typename T>void iter(T* array, size_t length, void (*func)(T&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}
template <typename T>void iter(T* array, size_t length, void (*func)(T const&)) { for (size_t i = 0; i < length; i++) { func(array[i]); }}
#endif#include <iostream>#include <string>#include "iter.hpp"
template <typename T>void print(T const& elem) { std::cout << elem << " ";}
template <typename T>void doubleValue(T& elem) { elem *= 2;}
int main() { int arr[] = {1, 2, 3, 4, 5};
std::cout << "Original: "; iter(arr, 5, print<int>); std::cout << std::endl;
iter(arr, 5, doubleValue<int>);
std::cout << "Doubled: "; iter(arr, 5, print<int>); std::cout << std::endl;
std::string strs[] = {"Hello", "World"}; std::cout << "Strings: "; iter(strs, 2, print<std::string>); std::cout << std::endl;
return 0;}Exercise 02: Array
Section titled “Exercise 02: Array”Subject Analysis
Section titled “Subject Analysis”Implement a class template Array<T> that:
- Stores elements of any type T
- Supports default construction (empty array)
- Supports size construction with elements initialized by default
- Full OCF (copy constructor, assignment, destructor)
operator[]with bounds checking (throws exception)size()member function
Key constraints:
- Memory allocated with
new[] - Must use exceptions for out-of-bounds
- Deep copy required (changes to copy don’t affect original)
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What members do we need?
T* _array- dynamically allocated arrayunsigned int _size- number of elements
-
Why deep copy?
- Each Array owns its own memory
- Shallow copy = two Arrays pointing to same memory = disaster
-
How to initialize elements by default?
new T[n]()- the()triggers value initialization- Integers become 0, pointers become NULL, objects use default constructor
-
Implementation in header only - why?
- Templates require implementation visible at instantiation
- Compiler generates code when you use
Array<int>,Array<std::string>, etc. - Can’t be in .cpp file (linker won’t find it)
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Class skeleton with members
#ifndef ARRAY_HPP#define ARRAY_HPP
#include <stdexcept> // std::out_of_range
template <typename T>class Array {private: T* _array; unsigned int _size;
public: // Constructors & Destructor Array(); Array(unsigned int n); Array(const Array& other); ~Array();
// Assignment Array& operator=(const Array& other);
// Element access T& operator[](unsigned int index); const T& operator[](unsigned int index) const;
// Size getter unsigned int size() const;};
#endifStage 2: Default and size constructors
template <typename T>Array<T>::Array() : _array(NULL), _size(0) {}
template <typename T>Array<T>::Array(unsigned int n) : _array(new T[n]()), _size(n) {}Stage 3: Copy constructor and destructor
template <typename T>Array<T>::Array(const Array& other) : _array(NULL), _size(0) { *this = other; // Use assignment operator}
template <typename T>Array<T>::~Array() { delete[] _array;}Stage 4: Assignment operator (deep copy)
template <typename T>Array<T>& Array<T>::operator=(const Array& other) { if (this != &other) { delete[] _array; // Free old memory _size = other._size; _array = new T[_size]; for (unsigned int i = 0; i < _size; i++) { _array[i] = other._array[i]; } } return *this;}Stage 5: Element access with bounds checking
template <typename T>T& Array<T>::operator[](unsigned int index) { if (index >= _size) { throw std::out_of_range("Array index out of bounds"); } return _array[index];}
template <typename T>const T& Array<T>::operator[](unsigned int index) const { if (index >= _size) { throw std::out_of_range("Array index out of bounds"); } return _array[index];}
template <typename T>unsigned int Array<T>::size() const { return _size;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Value initialization:
| Syntax | Effect |
|---|---|
new T[n] | Default initialization (garbage for primitives) |
new T[n]() | Value initialization (0 for primitives) |
// Without () - contains garbageint* arr1 = new int[5]; // arr1[0] = ??? (undefined)
// With () - initialized to zeroint* arr2 = new int[5](); // arr2[0] = 0Deep copy in assignment:
template <typename T>Array<T>& Array<T>::operator=(const Array& other) { if (this != &other) { // Self-assignment check delete[] _array; // Free existing memory _size = other._size; // Copy size _array = new T[_size]; // Allocate new memory for (unsigned int i = 0; i < _size; i++) { _array[i] = other._array[i]; // Copy each element } } return *this;}Why two operator[] overloads?
// Non-const: for modifying elementsT& operator[](unsigned int index);arr[0] = 42; // Uses non-const
// Const: for reading from const Arrayconst T& operator[](unsigned int index) const;const Array<int> arr(5);int x = arr[0]; // Uses const versionCommon Pitfalls
Section titled “Common Pitfalls”1. Shallow copy:
// WRONG: Both arrays point to same memoryArray(const Array& other) { _size = other._size; _array = other._array; // Shallow copy!}// When one is destroyed, other has dangling pointer
// RIGHT: Allocate new memory and copy elementsArray(const Array& other) : _array(NULL), _size(0) { *this = other; // Uses deep-copy assignment}2. Missing value initialization:
// WRONG: Garbage values for primitivesArray(unsigned int n) : _array(new T[n]), _size(n) {}
// RIGHT: Value-initialized (zeros for int, etc.)Array(unsigned int n) : _array(new T[n]()), _size(n) {}3. Not checking self-assignment:
// WRONG: Deletes own data before copyingArray& operator=(const Array& other) { delete[] _array; // Oops, deleted our own data! _array = new T[other._size]; // ... copy from other (which is now garbage)}
// RIGHT: Check firstArray& operator=(const Array& other) { if (this != &other) { delete[] _array; // ... safe to copy } return *this;}4. Using int instead of unsigned int:
// WRONG: Signed comparison can be problematicif (index >= _size) // Warning: comparing signed and unsigned
// Subject specifies unsigned intT& operator[](unsigned int index);5. Implementation in .cpp file:
// WRONG: Linker errortemplate <typename T>Array<T>::Array() { ... }
// RIGHT: Implementation must be in header// Array.hpp (or Array.tpp included by .hpp)template <typename T>Array<T>::Array() { ... }Testing Tips
Section titled “Testing Tips”#include <iostream>#include "Array.hpp"
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 5 array: " << arr.size() << std::endl;
// Test value initialization std::cout << "Initial values: "; for (unsigned int i = 0; i < arr.size(); i++) { std::cout << arr[i] << " "; } std::cout << std::endl;
// Test modification for (unsigned int i = 0; i < arr.size(); i++) { arr[i] = i * 10; } std::cout << "After modification: "; for (unsigned int i = 0; i < arr.size(); i++) { std::cout << arr[i] << " "; } std::cout << std::endl;
// Test deep copy Array<int> copy = arr; copy[0] = 999; std::cout << "Original[0]: " << arr[0] << std::endl; std::cout << "Copy[0]: " << copy[0] << std::endl;
// Test bounds checking try { arr[100] = 42; } catch (std::exception& e) { std::cout << "Exception: " << e.what() << std::endl; }
// Test with different type Array<std::string> strings(3); strings[0] = "Hello"; strings[1] = "World"; strings[2] = "!"; for (unsigned int i = 0; i < strings.size(); i++) { std::cout << strings[i] << " "; } std::cout << std::endl;
return 0;}Test bash script:
c++ -Wall -Wextra -Werror -std=c++98 main.cpp -o array_test./array_test
# Expected output:# Empty size: 0# Size 5 array: 5# Initial values: 0 0 0 0 0# After modification: 0 10 20 30 40# Original[0]: 0# Copy[0]: 999# Exception: Array index out of bounds# Hello World !Final Code
Section titled “Final Code”#ifndef ARRAY_HPP#define ARRAY_HPP
#include <stdexcept>
template <typename T>class Array {private: T* _array; unsigned int _size;
public: Array(); Array(unsigned int n); Array(const Array& other); ~Array();
Array& operator=(const Array& other);
T& operator[](unsigned int index); const T& operator[](unsigned int index) const;
unsigned int size() const;};
// Default constructortemplate <typename T>Array<T>::Array() : _array(NULL), _size(0) {}
// Size constructor with value initializationtemplate <typename T>Array<T>::Array(unsigned int n) : _array(new T[n]()), _size(n) {}
// Copy constructortemplate <typename T>Array<T>::Array(const Array& other) : _array(NULL), _size(0) { *this = other;}
// Destructortemplate <typename T>Array<T>::~Array() { delete[] _array;}
// Assignment operator (deep copy)template <typename T>Array<T>& Array<T>::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;}
// Element access (non-const)template <typename T>T& Array<T>::operator[](unsigned int index) { if (index >= _size) { throw std::out_of_range("Array index out of bounds"); } return _array[index];}
// Element access (const)template <typename T>const T& Array<T>::operator[](unsigned int index) const { if (index >= _size) { throw std::out_of_range("Array index out of bounds"); } return _array[index];}
// Size gettertemplate <typename T>unsigned int Array<T>::size() const { return _size;}
#endif#include <iostream>#include <string>#include "Array.hpp"
int main() { // Test empty array Array<int> empty; std::cout << "Empty size: " << empty.size() << std::endl;
// Test size constructor Array<int> arr(5); std::cout << "Values initialized to: "; for (unsigned int i = 0; i < arr.size(); i++) std::cout << arr[i] << " "; std::cout << std::endl;
// Test modification for (unsigned int i = 0; i < arr.size(); i++) arr[i] = i * 10;
// Test deep copy Array<int> copy(arr); copy[0] = 999; std::cout << "Original[0]=" << arr[0] << ", Copy[0]=" << copy[0] << std::endl;
// Test exception try { std::cout << arr[100] << std::endl; } catch (std::exception& e) { std::cout << "Exception caught: " << e.what() << std::endl; }
return 0;}Module 07 Summary: Templates
Section titled “Module 07 Summary: Templates”| Concept | Key Point |
|---|---|
| Function template | template <typename T> before function |
| Class template | template <typename T> before class |
| Instantiation | Compiler generates code for each type used |
| Header-only | Templates must be defined where used |
| Type requirements | T must support operations used on it |
Template syntax reference:
// Function templatetemplate <typename T>T const& min(T const& a, T const& b);
// Class templatetemplate <typename T>class Array { T* data;public: Array(); T& operator[](unsigned int i);};
// Method definition outside classtemplate <typename T>Array<T>::Array() : data(NULL) {}
// Using templatesArray<int> intArray(10);Array<std::string> strArray(5);