Module 07: C++ Templates
Key Concepts:
- Function templates
- Class templates
- Template instantiation
- Template specialization
Why This Matters
Section titled “Why This Matters”Templates enable generic programming—writing code once that works with any data type. The compiler generates specific versions as needed.
Templates power the entire Standard Template Library (STL)—every vector<T>, map<K,V>, and algorithm uses templates.
Why Templates? (Historical Context)
Section titled “Why Templates? (Historical Context)”Before C++ templates, C programmers used parametric macros to write generic code:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int result = MAX(3, 5); // Worksdouble d = MAX(3.14, 2.71); // WorksBut macros have serious problems:
- No type safety:
MAX("hello", 42)compiles but is nonsense - Text substitution bugs:
MAX(i++, j)incrementsitwice ifi > j - No scope: Macros pollute the global namespace
- No debugging: Errors point to expanded code, not the macro
Templates solve all these problems. The compiler:
- Checks types at compile time
- Generates real functions (no text substitution)
- Respects scope rules
- Provides (albeit verbose) type-checked error messages
The “Code with Holes” Mental Model
Section titled “The “Code with Holes” Mental Model”Think of templates as code patterns with holes (placeholders) that the compiler fills in:
template <typename T> // "T" is the holeT max(T a, T b) { // Pattern with holes return (a > b) ? a : b;}
max(3, 5); // Compiler fills hole with "int"max(3.14, 2.71); // Compiler fills hole with "double"When you use a template, the compiler instantiates it: it takes your pattern, fills in the holes with actual types, and generates real code. This is compile-time code generation, not runtime polymorphism.
Understanding Template Syntax
Section titled “Understanding Template Syntax”This module introduces templates. Here’s the new syntax explained:
The template <> Keyword
Section titled “The template <> Keyword”template <typename T>T max(T a, T b) { return (a > b) ? a : b; }template <>declares that what follows is a templatetypename T(orclass T) declares a type parameter calledTTcan be any type:int,double,std::string, your own class, etc.- When you use the template, the compiler generates code for the specific type
typenameandclassare interchangeable in C++98 (both mean the same thing)
The <> Angle Brackets (Template Parameters)
Section titled “The <> Angle Brackets (Template Parameters)”max<int>(3, 5); // Explicitly use intmax(3.14, 2.71); // Compiler deduces doubleArray<int> intArray; // Class template instantiation<>enclose template arguments- For functions, you can let the compiler deduce the type from arguments
- For classes, you must explicitly specify the type
- Read
Array<int>as “Array of int”
The :: Scope with Templates
Section titled “The :: Scope with Templates”std::vector<int>::iterator it;::with templates accesses nested typesvector<int>::iteratormeans “the iterator type inside vector-of-int”- Template classes can have their own nested types (types that depend on the template parameter)
The typename Keyword (Disambiguator)
Section titled “The typename Keyword (Disambiguator)”typename std::vector<T>::iterator it; // Tell compiler this is a typetypenamebeforeClassName::NestedTypetells the compiler “this is a type, not a value”- Inside templates, the compiler doesn’t know if
vector<T>::iteratoris a type or static member typenameremoves this ambiguity - it says “trust me, this is a type name”- Required when accessing nested types of template-dependent classes
Multiple Template Parameters
Section titled “Multiple Template Parameters”template <typename T, typename U>T convert(U value) { return static_cast<T>(value); }- You can have multiple template parameters separated by commas
Tis the return type,Uis the input type- Usage:
convert<int>(3.14)explicitly specifies T, compiler deduces U
Default Template Parameters
Section titled “Default Template Parameters”template <typename T = int>class Stack { };
Stack<> intStack; // Uses default: intStack<double> dblStack; // Explicit: double= intprovides a default type parameter- If not specified, the default is used
- Use
<>when you want the default (not justStack)
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() { /* ... */ }The .tpp naming convention: Using .tpp (template implementation) instead of .cpp clearly identifies template implementation files in your codebase. When you see a .tpp file, you immediately know it contains template definitions that will be included in a header.
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. Template Specialization
Section titled “9. Template Specialization”Sometimes you need different behavior for specific types. Template specialization lets you provide custom implementations for particular template arguments.
Complete Specialization (Functions and Classes)
Section titled “Complete Specialization (Functions and Classes)”// Generic templatetemplate <typename T>void print(T value) { std::cout << value << std::endl;}
// Complete specialization for booltemplate <>void print<bool>(bool value) { std::cout << (value ? "true" : "false") << std::endl;}
// Usageprint(42); // Uses generic: prints "42"print(3.14); // Uses generic: prints "3.14"print(true); // Uses specialization: prints "true"Class Template Specialization
Section titled “Class Template Specialization”// Generic templatetemplate <typename T>class Storage { T _data;public: void store(T val) { _data = val; } T retrieve() { return _data; }};
// Complete specialization for char*template <>class Storage<char*> { std::string _data; // Store as string for safetypublic: void store(char* val) { _data = val; } const char* retrieve() { return _data.c_str(); }};Partial Specialization (Classes Only)
Section titled “Partial Specialization (Classes Only)”Partial specialization provides a template for a subset of types (e.g., all pointers):
// Generic templatetemplate <typename T>class Container {public: void info() { std::cout << "Generic container" << std::endl; }};
// Partial specialization for all pointer typestemplate <typename T>class Container<T*> {public: void info() { std::cout << "Pointer container" << std::endl; }};
// UsageContainer<int> c1; // Uses genericContainer<int*> c2; // Uses pointer specializationContainer<double*> c3; // Uses pointer specializationNote: Function templates cannot be partially specialized. If you need partial specialization behavior for functions, use overloading or a helper class template.
When to Use Specialization
Section titled “When to Use Specialization”- Optimization: Provide faster implementations for specific types
- Special behavior: Handle types that don’t work with the generic implementation
- Type traits: Build compile-time type information (like
is_pointerabove)
10. Debugging Template Errors
Section titled “10. Debugging Template Errors”Template error messages are notoriously difficult to read. A small mistake can generate pages of errors. This is a known challenge with templates, not a personal failure.
Tips for Debugging
Section titled “Tips for Debugging”-
Read from the bottom up: The actual error is often near the end; earlier messages show instantiation context
-
Look for “instantiated from”: This tells you WHERE the template was used, helping you trace the problem
-
Test with concrete types first: If your template doesn’t work, try writing the code for a specific type (like
int) first -
Check required operations: If your template uses
<, the type must supportoperator<
// This template requires operator< to exist for type Ttemplate <typename T>T min(T a, T b) { return (a < b) ? a : b;}
// This will fail with a confusing error:struct NoCompare { int x; };NoCompare a, b;min(a, b); // Error: no operator< for NoCompare- Simplify: Comment out parts of your template to isolate the problem
11. Common Template Patterns
Section titled “11. 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: doubleExercise 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);Quick 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
Related Concepts
Section titled “Related Concepts”Continue your C++ journey:
- Previous: Module 06: C++ Casts - Review type conversions
- Next: Module 08: STL Containers - Learn the containers built with templates
Key Terms from This Module
Section titled “Key Terms from This Module”Visit the Glossary for definitions of: