C++ STL Complete Guide
Standard Library

C++ STL: Complete Guide to Standard Template Library

Master C++ Standard Template Library (STL): containers, algorithms, iterators, functors. Learn with practical examples and best practices for efficient programming.

Containers

Data Structures

Algorithms

Sort, Search, Transform

Iterators

Container Access

Functors

Function Objects

Introduction to STL

The Standard Template Library (STL) is a powerful set of C++ template classes that provide common data structures and algorithms. STL is a core part of C++ Standard Library and revolutionized C++ programming with generic programming concepts.

Why Use STL?
  • Code reusability and standardization
  • Type safety with templates
  • Efficient, well-tested implementations
  • Reduces development time
  • Portable across compilers and platforms
  • Follows consistent design patterns
STL Components
  • Containers: Store and organize data
  • Algorithms: Perform operations on data
  • Iterators: Access container elements
  • Functors: Function objects
  • Allocators: Memory management
  • Adaptors: Modify container interfaces
STL Architecture
Containers
vector, list, map, set
Iterators
begin(), end(), ++, --
Algorithms
sort(), find(), transform()
Functors
function objects

Algorithms use iterators to operate on containers

C++ STL Containers Comparison

The following table compares different STL containers with their characteristics, time complexities, and use cases:

Container Header Characteristics Time Complexity Use Cases
vector #include <vector> Dynamic array, random access Access: O(1) Insert/Delete: O(n) Default choice, when need array-like
deque #include <deque> Double-ended queue, fast ends Push/Pop ends: O(1) Middle ops: O(n) Queue/stack, sliding window
list #include <list> Doubly linked list Insert/Delete: O(1) Access: O(n) Frequent insertions/deletions
forward_list #include <forward_list> Singly linked list Insert/Delete: O(1) Access: O(n) Memory efficient list
array #include <array> Fixed-size array (C++11) All ops: O(1) Fixed size, stack allocation
set #include <set> Unique keys, sorted Most ops: O(log n) Unique collection, sorted
multiset #include <set> Duplicate keys, sorted Most ops: O(log n) Sorted bag, with duplicates
map #include <map> Key-value pairs, sorted keys Most ops: O(log n) Dictionary, associative array
multimap #include <map> Multiple values per key, sorted Most ops: O(log n) One-to-many mapping
unordered_set #include <unordered_set> Unique keys, hashed Avg: O(1) Worst: O(n) Fast lookup, no ordering
unordered_map #include <unordered_map> Key-value pairs, hashed Avg: O(1) Worst: O(n) Fast dictionary, no ordering
stack #include <stack> LIFO (uses deque by default) All ops: O(1) Function calls, undo/redo
queue #include <queue> FIFO (uses deque by default) All ops: O(1) BFS, task scheduling
priority_queue #include <queue> Max-heap (uses vector by default) Push/Pop: O(log n) Top: O(1) Dijkstra, scheduling

1. Sequence Containers

Sequence containers store elements in a linear sequence. They maintain the order of insertion and allow access by position.

Common Sequence Containers
vector
vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
list
list<int> l = {1, 2, 3};
l.push_front(0);
l.pop_front();
deque
deque<int> d = {1, 2, 3};
d.push_front(0);
d.push_back(4);
Sequence Containers Examples
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <array>
#include <forward_list>
#include <algorithm>
#include <string>
using namespace std;

void demonstrateVector() {
    cout << "=== vector (Dynamic Array) ===" << endl;
    
    // 1. Creation and initialization
    vector<int> v1;                     // Empty vector
    vector<int> v2(5, 10);              // 5 elements, each 10
    vector<int> v3 = {1, 2, 3, 4, 5};   // Initializer list
    vector<int> v4(v3.begin(), v3.end()); // From iterator range
    
    // 2. Basic operations
    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);
    
    cout << "Size: " << v1.size() << endl;
    cout << "Capacity: " << v1.capacity() << endl;
    cout << "Empty? " << (v1.empty() ? "Yes" : "No") << endl;
    
    // 3. Access elements
    cout << "Elements: ";
    for (int i = 0; i < v1.size(); i++) {
        cout << v1[i] << " ";          // No bounds checking
    }
    cout << endl;
    
    cout << "Using at(): ";
    for (int i = 0; i < v1.size(); i++) {
        cout << v1.at(i) << " ";       // With bounds checking
    }
    cout << endl;
    
    cout << "Front: " << v1.front() << endl;
    cout << "Back: " << v1.back() << endl;
    
    // 4. Modifying vector
    v1.pop_back();                     // Remove last element
    v1.insert(v1.begin() + 1, 15);     // Insert at position
    v1.erase(v1.begin() + 1);          // Remove at position
    v1.clear();                        // Remove all elements
    
    // 5. Reserve and shrink
    vector<int> v5;
    v5.reserve(100);                   // Pre-allocate memory
    for (int i = 0; i < 50; i++) {
        v5.push_back(i);
    }
    v5.shrink_to_fit();                // Reduce capacity to size
    
    cout << endl;
}

void demonstrateList() {
    cout << "=== list (Doubly Linked List) ===" << endl;
    
    list<string> names = {"Alice", "Bob", "Charlie"};
    
    // 1. Basic operations
    names.push_back("David");
    names.push_front("Zoe");
    names.pop_back();
    names.pop_front();
    
    // 2. Iteration
    cout << "Names: ";
    for (auto it = names.begin(); it != names.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 3. List-specific operations
    list<int> lst1 = {1, 2, 3, 4, 5};
    list<int> lst2 = {10, 20, 30};
    
    // Splice - move elements from one list to another
    auto it = lst1.begin();
    advance(it, 2);                    // Move iterator to position 2
    lst1.splice(it, lst2);             // Insert lst2 into lst1
    
    cout << "After splice: ";
    for (int num : lst1) {
        cout << num << " ";
    }
    cout << endl;
    
    // Remove elements
    lst1.remove(3);                    // Remove all 3s
    lst1.remove_if([](int n) {         // Remove if condition true
        return n % 2 == 0;             // Remove even numbers
    });
    
    // Sort and unique (list has member functions)
    list<int> lst3 = {5, 3, 1, 4, 2};
    lst3.sort();                       // O(n log n)
    lst3.unique();                     // Remove consecutive duplicates
    
    cout << endl;
}

void demonstrateDeque() {
    cout << "=== deque (Double-Ended Queue) ===" << endl;
    
    deque<int> dq;
    
    // Operations at both ends
    dq.push_back(10);
    dq.push_back(20);
    dq.push_front(5);
    dq.push_front(1);
    
    cout << "Deque: ";
    for (int num : dq) {
        cout << num << " ";
    }
    cout << endl;
    
    cout << "Front: " << dq.front() << endl;
    cout << "Back: " << dq.back() << endl;
    
    dq.pop_front();
    dq.pop_back();
    
    // Random access
    cout << "Element at index 1: " << dq[1] << endl;
    cout << "Element at index 1 (at): " << dq.at(1) << endl;
    
    cout << endl;
}

void demonstrateArray() {
    cout << "=== array (Fixed Size Array) ===" << endl;
    
    array<int, 5> arr = {1, 2, 3, 4, 5};  // Size fixed at compile time
    
    // Similar to vector interface
    cout << "Size: " << arr.size() << endl;
    cout << "Empty? " << (arr.empty() ? "Yes" : "No") << endl;
    cout << "Front: " << arr.front() << endl;
    cout << "Back: " << arr.back() << endl;
    
    // Access elements
    cout << "Elements: ";
    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // Fill array
    arr.fill(10);  // All elements become 10
    
    // Swap arrays
    array<int, 5> arr2 = {5, 4, 3, 2, 1};
    arr.swap(arr2);
    
    cout << endl;
}

void demonstrateForwardList() {
    cout << "=== forward_list (Singly Linked List) ===" << endl;
    
    forward_list<int> flist = {1, 2, 3, 4, 5};
    
    // Only forward traversal
    cout << "Forward list: ";
    for (int num : flist) {
        cout << num << " ";
    }
    cout << endl;
    
    // Operations (only front operations are efficient)
    flist.push_front(0);
    flist.pop_front();
    
    // Insert after a position
    auto it = flist.begin();
    advance(it, 2);
    flist.insert_after(it, 99);
    
    // Remove after a position
    flist.erase_after(it);
    
    // Remove elements
    flist.remove(3);                    // Remove all 3s
    flist.remove_if([](int n) {
        return n > 3;                   // Remove numbers > 3
    });
    
    cout << "After modifications: ";
    for (int num : flist) {
        cout << num << " ";
    }
    cout << endl << endl;
}

void performanceComparison() {
    cout << "=== Performance Comparison ===" << endl;
    cout << "Use vector when:" << endl;
    cout << "  - Need random access" << endl;
    cout << "  - Mostly add/remove at end" << endl;
    cout << "  - Memory contiguity important" << endl;
    cout << endl;
    
    cout << "Use list when:" << endl;
    cout << "  - Frequent insertions/deletions in middle" << endl;
    cout << "  - Don't need random access" << endl;
    cout << "  - Need stable iterators (iterators don't invalidate)" << endl;
    cout << endl;
    
    cout << "Use deque when:" << endl;
    cout << "  - Need efficient insertion/removal at both ends" << endl;
    cout << "  - Need random access" << endl;
    cout << "  - Don't need memory contiguity" << endl;
}

int main() {
    demonstrateVector();
    demonstrateList();
    demonstrateDeque();
    demonstrateArray();
    demonstrateForwardList();
    performanceComparison();
    
    return 0;
}
Choosing Sequence Containers:
  • vector: Default choice, good for most cases
  • list: When frequent insertions/deletions in middle
  • deque: When need fast operations at both ends
  • array: When fixed size known at compile time
  • forward_list: When memory is critical constraint
Common Mistakes:
  • Using [] operator without bounds checking
  • Invalidating iterators (vector reallocation)
  • Forgetting to check empty() before front()/back()
  • Using list when vector would be better
  • Not reserving capacity for large vectors

2. Associative Containers

Associative containers store elements in sorted order (or hashed) for fast lookup based on keys. They implement tree or hash table data structures.

Common Associative Containers
set
set<int> s = {3, 1, 2};
s.insert(4);
s.find(2);
map
map<string, int> m;
m["Alice"] = 25;
m.find("Alice");
unordered_set
unordered_set<int> us;
us.insert(10);
us.find(10);
unordered_map
unordered_map<string, int> um;
um["Bob"] = 30;
um.find("Bob");
Associative Containers Examples
#include <iostream>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <algorithm>
using namespace std;

void demonstrateSet() {
    cout << "=== set (Sorted Unique Elements) ===" << endl;
    
    // 1. Creation and initialization
    set<int> s1 = {5, 2, 8, 1, 9};
    set<int> s2(s1.begin(), s1.end());
    
    // Elements are automatically sorted
    cout << "Set elements (sorted): ";
    for (int num : s1) {
        cout << num << " ";
    }
    cout << endl;
    
    // 2. Insertion
    auto result = s1.insert(3);        // Returns pair
    if (result.second) {
        cout << "Inserted 3 successfully" << endl;
    }
    
    // Insert duplicate - fails
    result = s1.insert(2);             // 2 already exists
    if (!result.second) {
        cout << "2 already exists in set" << endl;
    }
    
    // 3. Finding elements
    auto it = s1.find(5);
    if (it != s1.end()) {
        cout << "Found 5 in set" << endl;
    }
    
    // count() returns 0 or 1 (since set has unique elements)
    if (s1.count(8) > 0) {
        cout << "8 exists in set" << endl;
    }
    
    // 4. Range operations
    auto lower = s1.lower_bound(3);    // First element >= 3
    auto upper = s1.upper_bound(7);    // First element > 7
    
    cout << "Elements in range [3, 7]: ";
    for (auto it = lower; it != upper; ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 5. Deletion
    s1.erase(2);                       // Erase by value
    it = s1.find(8);
    if (it != s1.end()) {
        s1.erase(it);                  // Erase by iterator
    }
    
    // Erase range
    s1.erase(s1.begin(), s1.find(5));  // Erase elements before 5
    
    cout << "Size after deletions: " << s1.size() << endl;
    cout << endl;
}

void demonstrateMultiset() {
    cout << "=== multiset (Sorted with Duplicates) ===" << endl;
    
    multiset<int> ms = {5, 2, 5, 1, 2, 3};
    
    cout << "Multiset elements: ";
    for (int num : ms) {
        cout << num << " ";
    }
    cout << endl;
    
    // Can insert duplicates
    ms.insert(5);
    ms.insert(5);
    
    cout << "Count of 5: " << ms.count(5) << endl;
    
    // Find all occurrences of a value
    auto range = ms.equal_range(5);    // Returns pair
    cout << "All 5s: ";
    for (auto it = range.first; it != range.second; ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // Erase all occurrences of a value
    ms.erase(5);                       // Erases all 5s
    
    cout << "After erasing all 5s: ";
    for (int num : ms) {
        cout << num << " ";
    }
    cout << endl << endl;
}

void demonstrateMap() {
    cout << "=== map (Sorted Key-Value Pairs) ===" << endl;
    
    // 1. Creation and initialization
    map<string, int> ageMap = {
        {"Alice", 25},
        {"Bob", 30},
        {"Charlie", 35}
    };
    
    // 2. Insertion methods
    ageMap["David"] = 28;              // Using [] operator
    ageMap.insert({"Eve", 22});        // Using insert()
    
    // insert() returns pair
    auto result = ageMap.insert({"Alice", 26}); // Alice already exists
    if (!result.second) {
        cout << "Alice already exists with age " << ageMap["Alice"] << endl;
    }
    
    // 3. Access elements
    cout << "Bob's age: " << ageMap["Bob"] << endl;
    
    // Using at() (throws exception if key doesn't exist)
    try {
        cout << "Charlie's age: " << ageMap.at("Charlie") << endl;
    } catch (const out_of_range& e) {
        cout << "Key not found!" << endl;
    }
    
    // 4. Checking existence
    if (ageMap.find("Frank") == ageMap.end()) {
        cout << "Frank not found in map" << endl;
    }
    
    // 5. Iteration
    cout << "\nAll entries (sorted by key):" << endl;
    for (const auto& pair : ageMap) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    // 6. Using iterators
    cout << "\nUsing iterators:" << endl;
    for (auto it = ageMap.begin(); it != ageMap.end(); ++it) {
        cout << it->first << ": " << it->second << endl;
    }
    
    // 7. Erasing elements
    ageMap.erase("Bob");               // Erase by key
    auto it = ageMap.find("Charlie");
    if (it != ageMap.end()) {
        ageMap.erase(it);              // Erase by iterator
    }
    
    // 8. Custom comparator
    map<string, int, greater<string>> reverseMap = {
        {"Alice", 25},
        {"Bob", 30},
        {"Charlie", 35}
    };
    
    cout << "\nReverse sorted map:" << endl;
    for (const auto& pair : reverseMap) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    cout << endl;
}

void demonstrateMultimap() {
    cout << "=== multimap (Multiple Values per Key) ===" << endl;
    
    multimap<string, string> courses;
    
    // Can have multiple values for same key
    courses.insert({"CS101", "Introduction to Programming"});
    courses.insert({"CS101", "Data Structures"});
    courses.insert({"CS101", "Algorithms"});
    courses.insert({"MATH101", "Calculus I"});
    courses.insert({"MATH101", "Linear Algebra"});
    
    // Finding all values for a key
    cout << "Courses for CS101:" << endl;
    auto range = courses.equal_range("CS101");
    for (auto it = range.first; it != range.second; ++it) {
        cout << "  " << it->second << endl;
    }
    
    cout << endl;
}

void demonstrateUnorderedContainers() {
    cout << "=== Unordered Containers (Hash Tables) ===" << endl;
    
    // 1. unordered_set
    unordered_set<string> us = {"apple", "banana", "cherry"};
    
    cout << "unordered_set elements: ";
    for (const string& fruit : us) {
        cout << fruit << " ";
    }
    cout << endl;
    
    // 2. unordered_map
    unordered_map<string, double> priceMap = {
        {"apple", 1.20},
        {"banana", 0.80},
        {"cherry", 3.50}
    };
    
    cout << "\nFruit prices:" << endl;
    for (const auto& pair : priceMap) {
        cout << pair.first << ": $" << pair.second << endl;
    }
    
    // 3. Hash statistics
    cout << "\nHash table statistics:" << endl;
    cout << "Bucket count: " << priceMap.bucket_count() << endl;
    cout << "Load factor: " << priceMap.load_factor() << endl;
    cout << "Max load factor: " << priceMap.max_load_factor() << endl;
    
    // 4. Iterating through buckets
    cout << "\nElements by bucket:" << endl;
    for (size_t i = 0; i < priceMap.bucket_count(); ++i) {
        cout << "Bucket " << i << ": ";
        for (auto it = priceMap.begin(i); it != priceMap.end(i); ++it) {
            cout << it->first << " ";
        }
        cout << endl;
    }
    
    // 5. Custom hash function (for custom types)
    struct Point {
        int x, y;
        
        bool operator==(const Point& other) const {
            return x == other.x && y == other.y;
        }
    };
    
    struct PointHash {
        size_t operator()(const Point& p) const {
            return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
        }
    };
    
    unordered_set<Point, PointHash> pointSet;
    pointSet.insert({1, 2});
    pointSet.insert({3, 4});
    
    cout << endl;
}

void containerComparison() {
    cout << "=== Ordered vs Unordered Containers ===" << endl;
    cout << "\nUse ordered containers (set/map) when:" << endl;
    cout << "  - Need elements in sorted order" << endl;
    cout << "  - Need to traverse in order" << endl;
    cout << "  - Need range queries (lower_bound, upper_bound)" << endl;
    cout << "  - Memory overhead of hash table is too high" << endl;
    
    cout << "\nUse unordered containers when:" << endl;
    cout << "  - Need faster lookup (O(1) average)" << endl;
    cout << "  - Don't care about order" << endl;
    cout << "  - Have good hash function for your type" << endl;
    cout << "  - Can tolerate O(n) worst-case performance" << endl;
}

int main() {
    demonstrateSet();
    demonstrateMultiset();
    demonstrateMap();
    demonstrateMultimap();
    demonstrateUnorderedContainers();
    containerComparison();
    
    return 0;
}

Ordered vs Unordered Containers

Use ordered containers (set/map) when you need sorted elements or range queries. Use unordered containers when you need faster lookups and don't care about order.

3. Container Adaptors

Container adaptors provide specialized interfaces for stacks, queues, and priority queues. They use underlying containers but restrict access patterns.

Container Adaptor Syntax
stack
stack<int> s;
s.push(10);
s.top();
s.pop();
queue
queue<int> q;
q.push(10);
q.front();
q.pop();
priority_queue
priority_queue<int> pq;
pq.push(10);
pq.top();
pq.pop();
Container Adaptors Examples
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <deque>
#include <functional>  // For greater<>
#include <string>
using namespace std;

void demonstrateStack() {
    cout << "=== stack (LIFO - Last In First Out) ===" << endl;
    
    // Default uses deque as underlying container
    stack<int> s1;
    
    // Can specify underlying container
    stack<int, vector<int>> s2;      // Uses vector
    stack<int, list<int>> s3;        // Uses list
    
    // Basic operations
    s1.push(10);
    s1.push(20);
    s1.push(30);
    
    cout << "Top element: " << s1.top() << endl;
    cout << "Size: " << s1.size() << endl;
    cout << "Empty? " << (s1.empty() ? "Yes" : "No") << endl;
    
    // Process stack (LIFO order)
    cout << "Stack elements (LIFO): ";
    while (!s1.empty()) {
        cout << s1.top() << " ";
        s1.pop();
    }
    cout << endl;
    
    // Practical example: Expression evaluation
    cout << "\nExpression Evaluation (Parentheses matching):" << endl;
    string expression = "((a + b) * (c - d))";
    stack<char> parenStack;
    bool balanced = true;
    
    for (char ch : expression) {
        if (ch == '(') {
            parenStack.push(ch);
        } else if (ch == ')') {
            if (parenStack.empty()) {
                balanced = false;
                break;
            }
            parenStack.pop();
        }
    }
    
    if (balanced && parenStack.empty()) {
        cout << "Expression is balanced" << endl;
    } else {
        cout << "Expression is not balanced" << endl;
    }
    
    cout << endl;
}

void demonstrateQueue() {
    cout << "=== queue (FIFO - First In First Out) ===" << endl;
    
    // Default uses deque as underlying container
    queue<string> q1;
    
    // Can specify underlying container
    queue<string, list<string>> q2;  // Uses list
    
    // Basic operations
    q1.push("Task 1");
    q1.push("Task 2");
    q1.push("Task 3");
    
    cout << "Front: " << q1.front() << endl;
    cout << "Back: " << q1.back() << endl;
    cout << "Size: " << q1.size() << endl;
    
    // Process queue (FIFO order)
    cout << "Processing queue (FIFO):" << endl;
    while (!q1.empty()) {
        cout << "Processing: " << q1.front() << endl;
        q1.pop();
    }
    
    // Practical example: BFS simulation
    cout << "\nBFS (Breadth-First Search) Simulation:" << endl;
    queue<int> bfsQueue;
    vector<bool> visited(6, false);
    vector<vector<int>> graph = {
        {1, 2},     // Node 0
        {0, 3, 4},  // Node 1
        {0, 5},     // Node 2
        {1},        // Node 3
        {1},        // Node 4
        {2}         // Node 5
    };
    
    bfsQueue.push(0);
    visited[0] = true;
    
    cout << "BFS traversal: ";
    while (!bfsQueue.empty()) {
        int current = bfsQueue.front();
        bfsQueue.pop();
        cout << current << " ";
        
        for (int neighbor : graph[current]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                bfsQueue.push(neighbor);
            }
        }
    }
    cout << endl << endl;
}

void demonstratePriorityQueue() {
    cout << "=== priority_queue (Max-Heap by default) ===" << endl;
    
    // Default: max-heap (largest element at top)
    priority_queue<int> maxHeap;
    
    // Min-heap: use greater<> comparator
    priority_queue<int, vector<int>, greater<int>> minHeap;
    
    // Basic operations for max-heap
    maxHeap.push(30);
    maxHeap.push(10);
    maxHeap.push(50);
    maxHeap.push(20);
    
    cout << "Max-heap elements (largest at top):" << endl;
    while (!maxHeap.empty()) {
        cout << maxHeap.top() << " ";
        maxHeap.pop();
    }
    cout << endl;
    
    // Min-heap example
    minHeap.push(30);
    minHeap.push(10);
    minHeap.push(50);
    minHeap.push(20);
    
    cout << "\nMin-heap elements (smallest at top):" << endl;
    while (!minHeap.empty()) {
        cout << minHeap.top() << " ";
        minHeap.pop();
    }
    cout << endl;
    
    // Custom comparator example
    struct Task {
        string name;
        int priority;  // Higher number = higher priority
        
        bool operator<(const Task& other) const {
            return priority < other.priority;  // For max-heap based on priority
        }
    };
    
    priority_queue<Task> taskQueue;
    taskQueue.push({"Low priority task", 1});
    taskQueue.push({"High priority task", 3});
    taskQueue.push({"Medium priority task", 2});
    
    cout << "\nProcessing tasks by priority:" << endl;
    while (!taskQueue.empty()) {
        Task task = taskQueue.top();
        taskQueue.pop();
        cout << task.name << " (Priority: " << task.priority << ")" << endl;
    }
    
    // Practical example: Merge k sorted arrays
    cout << "\nMerging k sorted arrays using min-heap:" << endl;
    vector<vector<int>> arrays = {
        {1, 4, 7},
        {2, 5, 8},
        {3, 6, 9}
    };
    
    // Element: (value, array_index, element_index)
    priority_queue<pair<int, pair<int, int>>, 
                   vector<pair<int, pair<int, int>>>,
                   greater<pair<int, pair<int, int>>>> minHeap2;
    
    // Push first element of each array
    for (int i = 0; i < arrays.size(); i++) {
        if (!arrays[i].empty()) {
            minHeap2.push({arrays[i][0], {i, 0}});
        }
    }
    
    vector<int> merged;
    while (!minHeap2.empty()) {
        auto current = minHeap2.top();
        minHeap2.pop();
        
        int value = current.first;
        int arrayIdx = current.second.first;
        int elementIdx = current.second.second;
        
        merged.push_back(value);
        
        // Push next element from same array
        if (elementIdx + 1 < arrays[arrayIdx].size()) {
            minHeap2.push({arrays[arrayIdx][elementIdx + 1], {arrayIdx, elementIdx + 1}});
        }
    }
    
    cout << "Merged array: ";
    for (int num : merged) {
        cout << num << " ";
    }
    cout << endl << endl;
}

void adaptorComparison() {
    cout << "=== Container Adaptor Comparison ===" << endl;
    
    cout << "\nstack (LIFO):" << endl;
    cout << "  - Operations: push(), pop(), top()" << endl;
    cout << "  - Underlying: deque (default), vector, list" << endl;
    cout << "  - Use cases: Function calls, undo/redo, parsing" << endl;
    
    cout << "\nqueue (FIFO):" << endl;
    cout << "  - Operations: push(), pop(), front(), back()" << endl;
    cout << "  - Underlying: deque (default), list" << endl;
    cout << "  - Use cases: BFS, task scheduling, buffering" << endl;
    
    cout << "\npriority_queue (Heap):" << endl;
    cout << "  - Operations: push(), pop(), top()" << endl;
    cout << "  - Underlying: vector (default), deque" << endl;
    cout << "  - Use cases: Dijkstra, scheduling, median maintenance" << endl;
}

int main() {
    demonstrateStack();
    demonstrateQueue();
    demonstratePriorityQueue();
    adaptorComparison();
    
    return 0;
}
stack
  • LIFO (Last In First Out)
  • Operations: push(), pop(), top()
  • Default container: deque
  • Use for: function calls, undo/redo
  • Time complexity: O(1) for all operations
queue
  • FIFO (First In First Out)
  • Operations: push(), pop(), front(), back()
  • Default container: deque
  • Use for: BFS, task scheduling
  • Time complexity: O(1) for all operations
priority_queue
  • Max-heap by default
  • Operations: push(), pop(), top()
  • Default container: vector
  • Use for: Dijkstra, scheduling
  • Time complexity: O(log n) push/pop

4. STL Algorithms

STL provides over 100 algorithms that operate on ranges of elements. Algorithms are template functions that work with iterators.

Common Algorithm Categories
Non-modifying
find(), count()
for_each(), all_of()
search(), mismatch()
Modifying
copy(), transform()
replace(), fill()
generate(), remove()
Sorting
sort(), stable_sort()
partial_sort()
nth_element()
Numeric
accumulate()
inner_product()
partial_sum()
adjacent_difference()
STL Algorithms Examples
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <numeric>     // For numeric algorithms
#include <functional>  // For function objects
#include <string>
#include <iterator>    // For back_inserter, etc.
#include <random>      // For random numbers
using namespace std;

void demonstrateNonModifyingAlgorithms() {
    cout << "=== Non-modifying Algorithms ===" << endl;
    
    vector<int> v = {1, 2, 3, 4, 5, 2, 3, 2, 7, 8};
    
    // 1. find() - find element
    auto it = find(v.begin(), v.end(), 3);
    if (it != v.end()) {
        cout << "Found 3 at position: " << distance(v.begin(), it) << endl;
    }
    
    // 2. count() - count occurrences
    int twos = count(v.begin(), v.end(), 2);
    cout << "Number of 2s: " << twos << endl;
    
    // 3. count_if() - count with condition
    int evens = count_if(v.begin(), v.end(), [](int n) {
        return n % 2 == 0;
    });
    cout << "Number of even numbers: " << evens << endl;
    
    // 4. all_of(), any_of(), none_of()
    bool allPositive = all_of(v.begin(), v.end(), [](int n) {
        return n > 0;
    });
    cout << "All positive? " << (allPositive ? "Yes" : "No") << endl;
    
    // 5. for_each() - apply function to each element
    cout << "Elements: ";
    for_each(v.begin(), v.end(), [](int n) {
        cout << n << " ";
    });
    cout << endl;
    
    // 6. search() - find subsequence
    vector<int> sub = {2, 3};
    auto subIt = search(v.begin(), v.end(), sub.begin(), sub.end());
    if (subIt != v.end()) {
        cout << "Found subsequence starting at: " << distance(v.begin(), subIt) << endl;
    }
    
    cout << endl;
}

void demonstrateModifyingAlgorithms() {
    cout << "=== Modifying Algorithms ===" << endl;
    
    vector<int> v = {1, 2, 3, 4, 5};
    
    // 1. copy() - copy elements
    vector<int> dest(5);
    copy(v.begin(), v.end(), dest.begin());
    cout << "Copied vector: ";
    for (int n : dest) cout << n << " ";
    cout << endl;
    
    // 2. copy_if() - copy with condition
    vector<int> evens;
    copy_if(v.begin(), v.end(), back_inserter(evens), [](int n) {
        return n % 2 == 0;
    });
    cout << "Even numbers: ";
    for (int n : evens) cout << n << " ";
    cout << endl;
    
    // 3. transform() - apply function and store result
    vector<int> squared(v.size());
    transform(v.begin(), v.end(), squared.begin(), [](int n) {
        return n * n;
    });
    cout << "Squared: ";
    for (int n : squared) cout << n << " ";
    cout << endl;
    
    // 4. replace() - replace elements
    vector<int> v2 = v;
    replace(v2.begin(), v2.end(), 3, 30);
    cout << "After replacing 3 with 30: ";
    for (int n : v2) cout << n << " ";
    cout << endl;
    
    // 5. fill() - fill with value
    vector<int> v3(5);
    fill(v3.begin(), v3.end(), 42);
    cout << "Filled with 42: ";
    for (int n : v3) cout << n << " ";
    cout << endl;
    
    // 6. generate() - generate using function
    vector<int> v4(5);
    int counter = 1;
    generate(v4.begin(), v4.end(), [&counter]() {
        return counter++ * 10;
    });
    cout << "Generated: ";
    for (int n : v4) cout << n << " ";
    cout << endl;
    
    // 7. remove() and erase-remove idiom
    vector<int> v5 = {1, 2, 3, 2, 4, 2, 5};
    auto newEnd = remove(v5.begin(), v5.end(), 2);
    v5.erase(newEnd, v5.end());
    cout << "After removing all 2s: ";
    for (int n : v5) cout << n << " ";
    cout << endl;
    
    cout << endl;
}

void demonstrateSortingAlgorithms() {
    cout << "=== Sorting Algorithms ===" << endl;
    
    vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    
    // 1. sort() - quick sort (not stable)
    sort(v.begin(), v.end());
    cout << "Sorted: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    // 2. stable_sort() - maintains order of equal elements
    vector<pair<int, string>> people = {
        {25, "Alice"},
        {30, "Bob"},
        {25, "Charlie"},
        {30, "David"}
    };
    
    stable_sort(people.begin(), people.end(), [](const auto& a, const auto& b) {
        return a.first < b.first;
    });
    
    cout << "Stable sorted by age: " << endl;
    for (const auto& p : people) {
        cout << p.second << " (" << p.first << ")" << endl;
    }
    
    // 3. partial_sort() - sort first n elements
    vector<int> v2 = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    partial_sort(v2.begin(), v2.begin() + 3, v2.end());
    cout << "First 3 elements sorted: ";
    for (int n : v2) cout << n << " ";
    cout << endl;
    
    // 4. nth_element() - partition around nth element
    vector<int> v3 = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    auto middle = v3.begin() + v3.size() / 2;
    nth_element(v3.begin(), middle, v3.end());
    cout << "Median: " << *middle << endl;
    cout << "After nth_element (left <= median <= right): ";
    for (int n : v3) cout << n << " ";
    cout << endl;
    
    // 5. binary_search() - requires sorted range
    vector<int> sorted = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    bool found = binary_search(sorted.begin(), sorted.end(), 5);
    cout << "5 found in sorted vector? " << (found ? "Yes" : "No") << endl;
    
    // 6. lower_bound(), upper_bound() - find insertion points
    vector<int> v4 = {1, 2, 2, 2, 3, 4, 5};
    auto lb = lower_bound(v4.begin(), v4.end(), 2);  // First element >= 2
    auto ub = upper_bound(v4.begin(), v4.end(), 2);  // First element > 2
    cout << "Range of 2s: [" << distance(v4.begin(), lb) << ", " 
         << distance(v4.begin(), ub) << ")" << endl;
    
    cout << endl;
}

void demonstrateNumericAlgorithms() {
    cout << "=== Numeric Algorithms ===" << endl;
    
    vector<int> v = {1, 2, 3, 4, 5};
    
    // 1. accumulate() - sum of elements
    int sum = accumulate(v.begin(), v.end(), 0);
    cout << "Sum: " << sum << endl;
    
    // accumulate with custom operation (product)
    int product = accumulate(v.begin(), v.end(), 1, multiplies<int>());
    cout << "Product: " << product << endl;
    
    // 2. inner_product() - dot product
    vector<int> v2 = {2, 3, 4, 5, 6};
    int dotProduct = inner_product(v.begin(), v.end(), v2.begin(), 0);
    cout << "Dot product: " << dotProduct << endl;
    
    // 3. partial_sum() - cumulative sum
    vector<int> result(v.size());
    partial_sum(v.begin(), v.end(), result.begin());
    cout << "Partial sums: ";
    for (int n : result) cout << n << " ";
    cout << endl;
    
    // 4. adjacent_difference() - difference between adjacent elements
    vector<int> diff(v.size());
    adjacent_difference(v.begin(), v.end(), diff.begin());
    cout << "Adjacent differences: ";
    for (int n : diff) cout << n << " ";
    cout << endl;
    
    // 5. iota() - fill with increasing values (C++11)
    vector<int> v3(5);
    iota(v3.begin(), v3.end(), 10);  // Start from 10
    cout << "Iota from 10: ";
    for (int n : v3) cout << n << " ";
    cout << endl;
    
    cout << endl;
}

void demonstrateHeapAlgorithms() {
    cout << "=== Heap Algorithms ===" << endl;
    
    vector<int> v = {9, 5, 7, 1, 3, 2, 8};
    
    // 1. make_heap() - create max-heap
    make_heap(v.begin(), v.end());
    cout << "Max-heap: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    cout << "Max element: " << v.front() << endl;
    
    // 2. push_heap() - add element to heap
    v.push_back(10);
    push_heap(v.begin(), v.end());
    cout << "After pushing 10: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    // 3. pop_heap() - remove max element
    pop_heap(v.begin(), v.end());
    v.pop_back();
    cout << "After popping max: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    // 4. sort_heap() - sort heap
    sort_heap(v.begin(), v.end());
    cout << "Sorted heap: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    cout << endl;
}

void demonstrateSetAlgorithms() {
    cout << "=== Set Algorithms ===" << endl;
    
    vector<int> v1 = {1, 2, 3, 4, 5};
    vector<int> v2 = {3, 4, 5, 6, 7};
    vector<int> result;
    
    // Need sorted ranges for set algorithms
    sort(v1.begin(), v1.end());
    sort(v2.begin(), v2.end());
    
    // 1. set_union() - union of two sets
    result.resize(v1.size() + v2.size());
    auto unionEnd = set_union(v1.begin(), v1.end(), 
                              v2.begin(), v2.end(), 
                              result.begin());
    result.erase(unionEnd, result.end());
    cout << "Union: ";
    for (int n : result) cout << n << " ";
    cout << endl;
    
    // 2. set_intersection() - intersection of two sets
    result.clear();
    set_intersection(v1.begin(), v1.end(), 
                     v2.begin(), v2.end(), 
                     back_inserter(result));
    cout << "Intersection: ";
    for (int n : result) cout << n << " ";
    cout << endl;
    
    // 3. set_difference() - elements in first but not second
    result.clear();
    set_difference(v1.begin(), v1.end(), 
                   v2.begin(), v2.end(), 
                   back_inserter(result));
    cout << "Difference (v1 - v2): ";
    for (int n : result) cout << n << " ";
    cout << endl;
    
    // 4. set_symmetric_difference() - elements in either but not both
    result.clear();
    set_symmetric_difference(v1.begin(), v1.end(), 
                             v2.begin(), v2.end(), 
                             back_inserter(result));
    cout << "Symmetric difference: ";
    for (int n : result) cout << n << " ";
    cout << endl;
    
    // 5. includes() - check if one set includes another
    bool includes = std::includes(v1.begin(), v1.end(), 
                                  result.begin(), result.end());
    cout << "v1 includes result? " << (includes ? "Yes" : "No") << endl;
    
    cout << endl;
}

void demonstrateMinMaxAlgorithms() {
    cout << "=== Min/Max Algorithms ===" << endl;
    
    vector<int> v = {5, 2, 8, 1, 9, 3};
    
    // 1. min_element(), max_element()
    auto minIt = min_element(v.begin(), v.end());
    auto maxIt = max_element(v.begin(), v.end());
    cout << "Min: " << *minIt << " at position " << distance(v.begin(), minIt) << endl;
    cout << "Max: " << *maxIt << " at position " << distance(v.begin(), maxIt) << endl;
    
    // 2. minmax_element() - find both min and max in one pass (C++11)
    auto minmax = minmax_element(v.begin(), v.end());
    cout << "Min: " << *minmax.first << ", Max: " << *minmax.second << endl;
    
    // 3. lexicographical_compare() - dictionary comparison
    string s1 = "apple";
    string s2 = "banana";
    bool lexLess = lexicographical_compare(s1.begin(), s1.end(), 
                                          s2.begin(), s2.end());
    cout << s1 << " < " << s2 << "? " << (lexLess ? "Yes" : "No") << endl;
    
    cout << endl;
}

int main() {
    demonstrateNonModifyingAlgorithms();
    demonstrateModifyingAlgorithms();
    demonstrateSortingAlgorithms();
    demonstrateNumericAlgorithms();
    demonstrateHeapAlgorithms();
    demonstrateSetAlgorithms();
    demonstrateMinMaxAlgorithms();
    
    return 0;
}
Algorithm Best Practices:
  • Prefer STL algorithms over hand-written loops (safer, clearer)
  • Use predicates (functions/lambdas) for custom comparisons
  • Remember iterator validity (some algorithms invalidate iterators)
  • Use back_inserter, front_inserter, inserter for output
  • Check algorithm preconditions (e.g., sorted ranges for binary search)
  • Use std::begin() and std::end() for C-style arrays

5. Iterators and Functors

Iterators provide a uniform way to access container elements. Functors (function objects) allow treating functions as objects.

Iterator Categories
  • Input: Read only, forward
  • Output: Write only, forward
  • Forward: Read/write, forward
  • Bidirectional: Forward/backward
  • Random Access: Direct access
  • Contiguous: C++17, contiguous memory
Functor Types
  • Function Objects: Classes with operator()
  • Function Pointers: Traditional C functions
  • Lambdas: C++11 anonymous functions
  • std::function: Type-erased callable
  • Binders: bind(), bind_front(), bind_back()
Iterators and Functors Examples
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <algorithm>
#include <functional>  // For function objects
#include <iterator>    // For iterator adaptors
#include <numeric>
#include <string>
using namespace std;

void demonstrateIterators() {
    cout << "=== Iterator Types and Operations ===" << endl;
    
    vector<int> v = {1, 2, 3, 4, 5};
    
    // 1. Different iterator types
    vector<int>::iterator it1;          // Read/write iterator
    vector<int>::const_iterator it2;    // Read-only iterator
    vector<int>::reverse_iterator it3;  // Reverse iterator
    
    // 2. Iterator operations
    it1 = v.begin();
    cout << "First element: " << *it1 << endl;
    ++it1;                              // Advance iterator
    cout << "Second element: " << *it1 << endl;
    it1 += 2;                           // Random access
    cout << "Fourth element: " << *it1 << endl;
    
    // 3. Iterator categories demonstration
    list<int> lst = {1, 2, 3, 4, 5};
    list<int>::iterator lit = lst.begin();
    ++lit;                              // OK for bidirectional
    // lit += 2;                        // Error: list iterator not random access
    
    // 4. Reverse iterators
    cout << "Reverse iteration: ";
    for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;
    
    // 5. Const iterators
    cout << "Const iteration: ";
    for (auto cit = v.cbegin(); cit != v.cend(); ++cit) {
        // *cit = 10;                   // Error: can't modify through const_iterator
        cout << *cit << " ";
    }
    cout << endl;
    
    // 6. Iterator traits
    using iter_type = vector<int>::iterator;
    cout << "Iterator category: ";
    if (is_same<iterator_traits<iter_type>::iterator_category,
                random_access_iterator_tag>::value) {
        cout << "Random Access Iterator" << endl;
    }
    
    // 7. Iterator invalidation
    vector<int> v2 = {1, 2, 3, 4, 5};
    auto invalidIt = v2.begin() + 2;
    v2.push_back(6);                    // May reallocate
    // cout << *invalidIt << endl;     // DANGER: iterator may be invalidated
    
    cout << endl;
}

void demonstrateIteratorAdaptors() {
    cout << "=== Iterator Adaptors ===" << endl;
    
    // 1. back_inserter, front_inserter, inserter
    vector<int> v = {1, 2, 3};
    list<int> lst;
    
    copy(v.begin(), v.end(), back_inserter(lst));      // push_back
    copy(v.begin(), v.end(), front_inserter(lst));     // push_front
    
    set<int> s;
    copy(v.begin(), v.end(), inserter(s, s.begin()));  // insert at position
    
    // 2. istream_iterator - read from input stream
    cout << "Enter numbers (Ctrl+D to end): ";
    vector<int> input;
    copy(istream_iterator<int>(cin), 
         istream_iterator<int>(), 
         back_inserter(input));
    
    cout << "You entered: ";
    copy(input.begin(), input.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
    
    // 3. ostream_iterator - write to output stream
    vector<string> words = {"Hello", "World", "C++", "STL"};
    cout << "Words: ";
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, " "));
    cout << endl;
    
    // 4. move_iterator (C++11) - move instead of copy
    vector<string> source = {"one", "two", "three"};
    vector<string> dest;
    
    copy(make_move_iterator(source.begin()),
         make_move_iterator(source.end()),
         back_inserter(dest));
    
    cout << "After move: source size = " << source.size() 
         << ", dest size = " << dest.size() << endl;
    
    cout << endl;
}

void demonstrateFunctionObjects() {
    cout << "=== Function Objects (Functors) ===" << endl;
    
    // 1. Predefined function objects
    vector<int> v = {1, 2, 3, 4, 5};
    
    // Using plus<> functor
    int sum = accumulate(v.begin(), v.end(), 0, plus<int>());
    cout << "Sum using plus: " << sum << endl;
    
    // Using multiplies<> functor
    int product = accumulate(v.begin(), v.end(), 1, multiplies<int>());
    cout << "Product using multiplies: " << product << endl;
    
    // Using greater<> for sorting in descending order
    sort(v.begin(), v.end(), greater<int>());
    cout << "Sorted descending: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    // 2. Custom function object
    struct IsEven {
        bool operator()(int n) const {
            return n % 2 == 0;
        }
    };
    
    int evenCount = count_if(v.begin(), v.end(), IsEven());
    cout << "Even numbers: " << evenCount << endl;
    
    // 3. Function object with state
    class RunningAverage {
    private:
        double total = 0;
        int count = 0;
    public:
        double operator()(double value) {
            total += value;
            count++;
            return total / count;
        }
    };
    
    vector<double> values = {1.0, 2.0, 3.0, 4.0, 5.0};
    RunningAverage avg;
    cout << "Running averages: ";
    for (double val : values) {
        cout << avg(val) << " ";
    }
    cout << endl;
    
    cout << endl;
}

void demonstrateLambdas() {
    cout << "=== Lambda Expressions (C++11) ===" << endl;
    
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 1. Basic lambda
    auto isEven = [](int n) { return n % 2 == 0; };
    int evens = count_if(v.begin(), v.end(), isEven);
    cout << "Even numbers: " << evens << endl;
    
    // 2. Lambda with capture by value
    int threshold = 5;
    auto greaterThan = [threshold](int n) { return n > threshold; };
    int aboveThreshold = count_if(v.begin(), v.end(), greaterThan);
    cout << "Numbers > " << threshold << ": " << aboveThreshold << endl;
    
    // 3. Lambda with capture by reference
    int sum = 0;
    for_each(v.begin(), v.end(), [&sum](int n) { sum += n; });
    cout << "Sum: " << sum << endl;
    
    // 4. Lambda with mutable (can modify captured values)
    int counter = 0;
    auto incrementer = [counter]() mutable { return ++counter; };
    cout << "Incrementer: " << incrementer() << " " 
         << incrementer() << " " << incrementer() << endl;
    
    // 5. Lambda as comparator
    sort(v.begin(), v.end(), [](int a, int b) {
        return a > b;  // Sort descending
    });
    cout << "Sorted descending: ";
    for (int n : v) cout << n << " ";
    cout << endl;
    
    // 6. Generic lambda (C++14)
    auto add = [](auto a, auto b) { return a + b; };
    cout << "add(5, 3) = " << add(5, 3) << endl;
    cout << "add(2.5, 3.7) = " << add(2.5, 3.7) << endl;
    cout << "add(string(\"Hello, \"), string(\"World!\")) = " 
         << add(string("Hello, "), string("World!")) << endl;
    
    cout << endl;
}

void demonstrateStdFunction() {
    cout << "=== std::function (Type-erased callable) ===" << endl;
    
    // 1. Storing different callables
    function<int(int, int)> operation;
    
    // Store lambda
    operation = [](int a, int b) { return a + b; };
    cout << "10 + 20 = " << operation(10, 20) << endl;
    
    // Store function pointer
    operation = plus<int>();
    cout << "10 + 20 = " << operation(10, 20) << endl;
    
    // 2. Function with different signatures
    function<void(int)> callback;
    
    callback = [](int x) { 
        cout << "Callback called with: " << x << endl; 
    };
    callback(42);
    
    // 3. Using in algorithms
    vector<function<bool(int)>> predicates;
    predicates.push_back([](int n) { return n % 2 == 0; });
    predicates.push_back([](int n) { return n > 5; });
    predicates.push_back([](int n) { return n < 10; });
    
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    for (const auto& pred : predicates) {
        int count = count_if(v.begin(), v.end(), pred);
        cout << "Predicate matches: " << count << " elements" << endl;
    }
    
    // 4. Function with bind
    auto add = [](int a, int b, int c) { return a + b + c; };
    auto add5 = bind(add, placeholders::_1, placeholders::_2, 5);
    cout << "add5(10, 20) = " << add5(10, 20) << endl;
    
    cout << endl;
}

void demonstrateCustomIterators() {
    cout << "=== Custom Iterator Example ===" << endl;
    
    // Simple custom iterator for demonstration
    class Range {
    private:
        int start, end;
    public:
        class Iterator {
        private:
            int current;
        public:
            using iterator_category = input_iterator_tag;
            using value_type = int;
            using difference_type = int;
            using pointer = int*;
            using reference = int&;
            
            Iterator(int pos) : current(pos) {}
            
            int operator*() const { return current; }
            Iterator& operator++() { ++current; return *this; }
            Iterator operator++(int) { Iterator temp = *this; ++current; return temp; }
            bool operator==(const Iterator& other) const { return current == other.current; }
            bool operator!=(const Iterator& other) const { return current != other.current; }
        };
        
        Range(int s, int e) : start(s), end(e) {}
        Iterator begin() const { return Iterator(start); }
        Iterator end() const { return Iterator(end); }
    };
    
    // Using custom iterator
    cout << "Range [1, 10): ";
    for (int n : Range(1, 10)) {
        cout << n << " ";
    }
    cout << endl;
    
    // Use with STL algorithms
    Range r(1, 6);
    int sum = accumulate(r.begin(), r.end(), 0);
    cout << "Sum of range [1, 6) = " << sum << endl;
}

int main() {
    demonstrateIterators();
    demonstrateIteratorAdaptors();
    demonstrateFunctionObjects();
    demonstrateLambdas();
    demonstrateStdFunction();
    demonstrateCustomIterators();
    
    return 0;
}
When to Use Each Callable Type:
  • Function objects: When you need state or type erasure isn't needed
  • Function pointers: For C compatibility or simple functions
  • Lambdas: Most common case - concise and can capture context
  • std::function: When you need to store different callables with same signature
  • Bind expressions: When you need to fix some arguments of a function

6. STL Best Practices and Common Mistakes

STL Best Practices
  • Prefer algorithms over hand-written loops
  • Use auto for iterator types (C++11)
  • Choose the right container for your use case
  • Use emplace operations when possible (C++11)
  • Reserve capacity for vectors when size is known
  • Use range-based for loops for simple iteration
  • Prefer unordered_map over map when order not needed
Common Mistakes
  • Iterator invalidation after container modification
  • Using wrong iterator category for algorithm
  • Forgetting to check empty() before front()/back()
  • Memory leaks with pointers in containers
  • Inefficient container choice for operations
  • Not using move semantics (C++11)
  • Undefined behavior with mismatched allocators
Good vs Bad STL Practices
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <algorithm>
#include <memory>
#include <string>
using namespace std;

// BAD PRACTICE examples
void demonstrateBadPractices() {
    cout << "=== BAD PRACTICES ===" << endl;
    
    // 1. Iterator invalidation
    vector<int> v = {1, 2, 3, 4, 5};
    auto it = v.begin() + 2;
    v.push_back(6);                    // May reallocate
    // cout << *it << endl;           // DANGER: iterator invalidated!
    
    // 2. Inefficient container choice
    list<int> lst;
    for (int i = 0; i < 1000000; ++i) {
        lst.push_back(i);
    }
    // BAD: Accessing elements in list by index
    for (size_t i = 0; i < lst.size(); ++i) {
        // Would need to traverse list each time - O(n²)
    }
    
    // 3. Memory leak with raw pointers
    vector<int*> ptrVec;
    ptrVec.push_back(new int(42));
    ptrVec.push_back(new int(84));
    // BAD: Forgot to delete!
    // Should use smart pointers instead
    
    // 4. Wrong algorithm assumptions
    vector<int> unsorted = {5, 2, 8, 1, 9};
    // BAD: binary_search requires sorted range
    bool found = binary_search(unsorted.begin(), unsorted.end(), 5);
    // Result is undefined!
    
    // 5. Not checking bounds
    vector<int> emptyVec;
    // BAD: Accessing empty container
    // int value = emptyVec.front();   // Undefined behavior
    // value = emptyVec[0];            // Undefined behavior
    
    cout << endl;
}

// GOOD PRACTICE examples
void demonstrateGoodPractices() {
    cout << "=== GOOD PRACTICES ===" << endl;
    
    // 1. Using algorithms over loops
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // BAD: Manual loop
    // int sum = 0;
    // for (size_t i = 0; i < v.size(); ++i) {
    //     sum += v[i];
    // }
    
    // GOOD: Using algorithm
    int sum = accumulate(v.begin(), v.end(), 0);
    cout << "Sum: " << sum << endl;
    
    // 2. Using auto for iterators (C++11)
    // BAD: Verbose iterator type
    // vector<int>::iterator it = v.begin();
    
    // GOOD: Using auto
    auto it = v.begin();
    cout << "First element: " << *it << endl;
    
    // 3. Using range-based for loops (C++11)
    cout << "Elements: ";
    for (int num : v) {                // GOOD: Clean and simple
        cout << num << " ";
    }
    cout << endl;
    
    // 4. Using emplace operations (C++11)
    vector<pair<string, int>> people;
    
    // BAD: Creating temporary
    // people.push_back(pair<string, int>("Alice", 25));
    
    // GOOD: Using emplace_back (constructs in-place)
    people.emplace_back("Alice", 25);
    people.emplace_back("Bob", 30);
    
    // 5. Reserving capacity
    vector<int> largeVector;
    largeVector.reserve(1000000);      // GOOD: Avoid reallocations
    for (int i = 0; i < 1000000; ++i) {
        largeVector.push_back(i);
    }
    
    // 6. Using smart pointers with containers
    vector<unique_ptr<int>> smartVec;
    smartVec.push_back(make_unique<int>(42));
    smartVec.push_back(make_unique<int>(84));
    // No memory leaks - automatically cleaned up
    
    // 7. Choosing right container
    // Need fast lookup, don't care about order
    unordered_map<string, int> ageMap; // GOOD: O(1) average lookup
    ageMap["Alice"] = 25;
    ageMap["Bob"] = 30;
    
    // Need sorted order
    map<string, int> sortedMap;        // GOOD: O(log n) lookup, sorted
    
    // 8. Using const correctness
    const vector<int> constVec = {1, 2, 3, 4, 5};
    // constVec.push_back(6);          // Error: good - prevents modification
    
    // Use const iterators for read-only access
    for (auto cit = constVec.cbegin(); cit != constVec.cend(); ++cit) {
        cout << *cit << " ";
    }
    cout << endl;
    
    // 9. Safe access patterns
    vector<int> maybeEmpty;
    
    // BAD: Unsafe
    // if (!maybeEmpty.empty()) {
    //     int first = maybeEmpty.front();
    // }
    
    // GOOD: Safe check
    if (!maybeEmpty.empty()) {
        int first = maybeEmpty.front();
        cout << "First element: " << first << endl;
    }
    
    // 10. Using algorithms with predicates
    auto isPrime = [](int n) -> bool {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; ++i) {
            if (n % i == 0) return false;
        }
        return true;
    };
    
    vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
    auto primeIt = find_if(numbers.begin(), numbers.end(), isPrime);
    if (primeIt != numbers.end()) {
        cout << "First prime: " << *primeIt << endl;
    }
    
    // 11. Move semantics (C++11)
    vector<string> source = {"large", "strings", "here"};
    vector<string> destination;
    
    // GOOD: Move instead of copy
    move(source.begin(), source.end(), back_inserter(destination));
    cout << "After move: source has " << source.size() 
         << " elements (moved-from state)" << endl;
    
    // 12. Structured bindings (C++17)
    map<string, int> scores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 92}};
    
    cout << "\nScores:" << endl;
    for (const auto& [name, score] : scores) {  // C++17
        cout << name << ": " << score << endl;
    }
    
    cout << endl;
}

void performanceTips() {
    cout << "=== Performance Tips ===" << endl;
    
    cout << "1. Use vector by default, switch only if needed" << endl;
    cout << "2. Reserve capacity for vectors when size known" << endl;
    cout << "3. Use unordered containers when order not needed" << endl;
    cout << "4. Prefer algorithms over hand-written loops" << endl;
    cout << "5. Use move semantics for large objects" << endl;
    cout << "6. Avoid unnecessary copies" << endl;
    cout << "7. Consider custom allocators for special cases" << endl;
    cout << "8. Profile before optimizing!" << endl;
}

int main() {
    demonstrateBadPractices();
    demonstrateGoodPractices();
    performanceTips();
    
    return 0;
}