cpp-know-hows

cpp related stuff

View on GitHub

Lambdas and Functional Programming

Overview

Lambda expressions (introduced in C++11) provide a concise way to create anonymous function objects. They’re essential for modern C++ and functional programming.

┌─────────────────────────────────────────────────────────┐
│                    LAMBDA EXPRESSIONS                   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  [capture](parameters) -> return_type { body }          │
│   ▲        ▲             ▲              ▲               │
│   │        │             │              │               │
│   │        │             │              └─ Function body│
│   │        │             └─ Return type (optional)      │
│   │        └─ Parameters                                │
│   └─ Capture clause                                     │
│                                                         │
│  EVOLUTION:                                             │
│  C++11: Basic lambdas                                   │
│  C++14: Generic lambdas, init-capture                   │
│  C++17: constexpr lambdas                               │
│  C++20: Template lambdas                                │
│  C++23: Explicit object parameter                       │
│                                                         │
└─────────────────────────────────────────────────────────┘

Basic Lambda Syntax

Simple Lambda

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    // Basic lambda
    auto greet = []() {
        std::cout << "Hello, World!\n";
    };
    greet();  // Call it
    
    // Lambda with parameters
    auto add = [](int a, int b) {
        return a + b;
    };
    int sum = add(5, 3);  // 8
    
    // Lambda with explicit return type
    auto divide = [](int a, int b) -> double {
        return static_cast<double>(a) / b;
    };
    
    // Inline lambda (common with STL algorithms)
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::for_each(vec.begin(), vec.end(), [](int x) {
        std::cout << x << ' ';
    });
    
    return 0;
}

Lambda Anatomy

Full Lambda Expression:

[capture_clause](parameters) mutable -> return_type { body }
 ▲              ▲            ▲        ▲              ▲
 │              │            │        │              │
 │              │            │        │              └─ Function body
 │              │            │        └─ Return type (optional if deducible)
 │              │            └─ mutable keyword (optional)
 │              └─ Parameter list
 └─ Capture clause (how to access outer variables)

Minimal Lambda:
[](){ }

Capture Clauses

Capture Modes

int main() {
    int x = 10;
    int y = 20;
    
    // [=] Capture all by value
    auto lambda1 = [=]() {
        return x + y;  // x and y are copies
    };
    
    // [&] Capture all by reference
    auto lambda2 = [&]() {
        x++;  // Modifies original x
        y++;  // Modifies original y
    };
    
    // [x] Capture x by value
    auto lambda3 = [x]() {
        return x * 2;  // x is a copy
    };
    
    // [&x] Capture x by reference
    auto lambda4 = [&x]() {
        x *= 2;  // Modifies original x
    };
    
    // [x, &y] Mixed capture
    auto lambda5 = [x, &y]() {
        // x is copy, y is reference
        return x + (++y);
    };
    
    // [=, &y] Capture all by value except y by reference
    auto lambda6 = [=, &y]() {
        return x + (++y);
    };
    
    // [&, x] Capture all by reference except x by value
    auto lambda7 = [&, x]() {
        y++;  // Modifies original y
        return x + y;  // x is a copy
    };
    
    return 0;
}

Visual: Capture Behavior

Original Variables:
┌─────┬─────┐
│ x=10│ y=20│
└─────┴─────┘

[=] Capture by Value:
Lambda object:
┌─────┬─────┐
│ x=10│ y=20│  (copies)
└─────┴─────┘

[&] Capture by Reference:
Lambda object:
┌────┬────┐
│ &x │ &y │  (pointers to originals)
└────┴────┘
     │    │
     ▼    ▼
┌─────┬─────┐
│ x=10│ y=20│  (original variables)
└─────┴─────┘

Capture Examples

#include <iostream>

int main() {
    int count = 0;
    
    // Capture by value (count is copied)
    auto increment_copy = [count]() mutable {
        count++;  // Modifies the copy
        std::cout << "Inside: " << count << '\n';
    };
    
    increment_copy();  // Inside: 1
    increment_copy();  // Inside: 2
    std::cout << "Outside: " << count << '\n';  // Outside: 0
    
    // Capture by reference (count is referenced)
    auto increment_ref = [&count]() {
        count++;  // Modifies original
        std::cout << "Inside: " << count << '\n';
    };
    
    increment_ref();  // Inside: 1
    increment_ref();  // Inside: 2
    std::cout << "Outside: " << count << '\n';  // Outside: 2
    
    return 0;
}

Init Capture (C++14)

int main() {
    int x = 10;
    
    // Initialize new variable in capture
    auto lambda = [y = x + 5]() {
        return y * 2;  // y is 15
    };
    
    // Move into lambda
    auto ptr = std::make_unique<int>(42);
    auto lambda2 = [p = std::move(ptr)]() {
        return *p;  // Owns the unique_ptr
    };
    
    // Complex initialization
    auto lambda3 = [vec = std::vector<int>{1, 2, 3}]() {
        return vec.size();
    };
    
    return 0;
}

Mutable Lambdas

Understanding mutable

int main() {
    int x = 10;
    
    // Without mutable: captured values are const
    auto lambda1 = [x]() {
        // x++;  // ERROR: x is const
        return x;
    };
    
    // With mutable: can modify captured values
    auto lambda2 = [x]() mutable {
        x++;  // OK: modifies the copy
        return x;
    };
    
    std::cout << lambda2() << '\n';  // 11
    std::cout << lambda2() << '\n';  // 12
    std::cout << x << '\n';          // 10 (original unchanged)
    
    return 0;
}

Visual: mutable Keyword

Without mutable:
Lambda object (const members):
┌─────────┐
│ const x │  Cannot modify
└─────────┘

With mutable:
Lambda object (non-const members):
┌─────┐
│  x  │  Can modify
└─────┘

Note: Only affects captured-by-value variables!
References are always modifiable.

Generic Lambdas (C++14)

Auto Parameters

#include <iostream>
#include <string>

int main() {
    // Generic lambda (works with any type)
    auto print = [](auto x) {
        std::cout << x << '\n';
    };
    
    print(42);           // int
    print(3.14);         // double
    print("Hello");      // const char*
    print(std::string("World"));  // std::string
    
    // Multiple auto parameters
    auto add = [](auto a, auto b) {
        return a + b;
    };
    
    auto sum1 = add(5, 3);        // int + int
    auto sum2 = add(1.5, 2.5);    // double + double
    auto sum3 = add(std::string("Hello"), std::string(" World"));  // string + string
    
    // Generic lambda with constraints
    auto multiply = [](auto a, auto b) -> decltype(a * b) {
        return a * b;
    };
    
    return 0;
}

Template Lambda (C++20)

#include <vector>
#include <iostream>

int main() {
    // Explicit template parameters
    auto print_container = []<typename T>(const std::vector<T>& vec) {
        for (const auto& elem : vec) {
            std::cout << elem << ' ';
        }
        std::cout << '\n';
    };
    
    std::vector<int> v1 = {1, 2, 3};
    std::vector<double> v2 = {1.1, 2.2, 3.3};
    
    print_container(v1);
    print_container(v2);
    
    // Perfect forwarding in lambda
    auto forward_call = []<typename... Args>(Args&&... args) {
        return std::make_tuple(std::forward<Args>(args)...);
    };
    
    return 0;
}

Lambda with STL Algorithms

Predicates

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // count_if with lambda predicate
    auto even_count = std::count_if(vec.begin(), vec.end(), 
        [](int x) { return x % 2 == 0; });
    
    // find_if
    auto it = std::find_if(vec.begin(), vec.end(),
        [](int x) { return x > 5; });
    
    // remove_if
    vec.erase(
        std::remove_if(vec.begin(), vec.end(),
            [](int x) { return x % 2 == 0; }),
        vec.end()
    );
    // vec now contains only odd numbers
    
    // sort with custom comparator
    std::vector<std::string> words = {"apple", "pie", "a", "hello"};
    std::sort(words.begin(), words.end(),
        [](const std::string& a, const std::string& b) {
            return a.length() < b.length();  // Sort by length
        });
    
    return 0;
}

Transform

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> squares(5);
    
    // Square each number
    std::transform(numbers.begin(), numbers.end(), squares.begin(),
        [](int x) { return x * x; });
    
    // In-place transform
    std::transform(numbers.begin(), numbers.end(), numbers.begin(),
        [](int x) { return x * 2; });
    
    return 0;
}

Accumulate with Lambda

#include <numeric>
#include <vector>
#include <string>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // Sum with lambda
    int sum = std::accumulate(vec.begin(), vec.end(), 0,
        [](int acc, int x) { return acc + x; });
    
    // Product
    int product = std::accumulate(vec.begin(), vec.end(), 1,
        [](int acc, int x) { return acc * x; });
    
    // Concatenate strings
    std::vector<std::string> words = {"Hello", " ", "World", "!"};
    std::string sentence = std::accumulate(words.begin(), words.end(), 
        std::string(""),
        [](const std::string& acc, const std::string& word) {
            return acc + word;
        });
    
    return 0;
}

Returning Lambdas

Lambda as Return Value

#include <functional>

// Return lambda from function
auto make_adder(int n) {
    return [n](int x) { return x + n; };
}

// Using std::function for type erasure
std::function<int(int)> make_multiplier(int n) {
    return [n](int x) { return x * n; };
}

int main() {
    auto add5 = make_adder(5);
    std::cout << add5(10) << '\n';  // 15
    std::cout << add5(20) << '\n';  // 25
    
    auto times3 = make_multiplier(3);
    std::cout << times3(4) << '\n';  // 12
    
    return 0;
}

Closure and Captured Variables

auto make_counter() {
    int count = 0;
    return [count]() mutable {
        return ++count;
    };
}

int main() {
    auto counter1 = make_counter();
    auto counter2 = make_counter();
    
    std::cout << counter1() << '\n';  // 1
    std::cout << counter1() << '\n';  // 2
    std::cout << counter2() << '\n';  // 1 (separate closure)
    
    return 0;
}

constexpr Lambdas (C++17)

Compile-Time Lambdas

int main() {
    // Implicitly constexpr if possible
    auto square = [](int x) { return x * x; };
    
    constexpr int result = square(5);  // Computed at compile time
    
    // Explicit constexpr
    constexpr auto add = [](int a, int b) constexpr {
        return a + b;
    };
    
    constexpr int sum = add(10, 20);  // Compile time
    
    // Use in constexpr context
    static_assert(square(4) == 16);
    
    return 0;
}

Recursive Lambdas

Using std::function

#include <functional>

int main() {
    // Factorial using recursive lambda
    std::function<int(int)> factorial = [&factorial](int n) {
        return n <= 1 ? 1 : n * factorial(n - 1);
    };
    
    std::cout << factorial(5) << '\n';  // 120
    
    return 0;
}

Using Y-Combinator (C++14)

auto Y = [](auto f) {
    return [f](auto... args) {
        return f(f, args...);
    };
};

int main() {
    auto factorial = Y([](auto self, int n) -> int {
        return n <= 1 ? 1 : n * self(self, n - 1);
    });
    
    std::cout << factorial(5) << '\n';  // 120
    
    return 0;
}

Advanced Lambda Patterns

Immediately Invoked Lambda Expression (IIFE)

int main() {
    // Initialize const variable with complex logic
    const int value = [](int x) {
        if (x < 0) return 0;
        if (x > 100) return 100;
        return x;
    }(42);  // Immediately invoked!
    
    // Useful for const initialization
    const auto data = [&]() {
        std::vector<int> temp;
        for (int i = 0; i < 10; ++i) {
            temp.push_back(i * i);
        }
        return temp;
    }();
    
    return 0;
}

Stateful Lambdas

#include <iostream>

int main() {
    // Lambda with state (via mutable capture)
    int seed = 42;
    auto random = [seed]() mutable {
        seed = (seed * 1103515245 + 12345) & 0x7fffffff;
        return seed;
    };
    
    std::cout << random() << '\n';
    std::cout << random() << '\n';
    std::cout << random() << '\n';
    
    return 0;
}

Lambda Overloading (C++17)

#include <variant>

// Helper for overloading lambdas
template<typename... Ts>
struct overload : Ts... {
    using Ts::operator()...;
};

template<typename... Ts>
overload(Ts...) -> overload<Ts...>;

int main() {
    std::variant<int, double, std::string> var = 42;
    
    std::visit(overload{
        [](int i) { std::cout << "int: " << i << '\n'; },
        [](double d) { std::cout << "double: " << d << '\n'; },
        [](const std::string& s) { std::cout << "string: " << s << '\n'; }
    }, var);
    
    return 0;
}

Functional Programming Patterns

Map, Filter, Reduce

#include <vector>
#include <algorithm>
#include <numeric>

template<typename T, typename F>
auto map(const std::vector<T>& vec, F func) {
    std::vector<decltype(func(vec[0]))> result;
    result.reserve(vec.size());
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), func);
    return result;
}

template<typename T, typename F>
auto filter(const std::vector<T>& vec, F pred) {
    std::vector<T> result;
    std::copy_if(vec.begin(), vec.end(), std::back_inserter(result), pred);
    return result;
}

template<typename T, typename F>
auto reduce(const std::vector<T>& vec, T init, F func) {
    return std::accumulate(vec.begin(), vec.end(), init, func);
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    // Map: square each number
    auto squares = map(numbers, [](int x) { return x * x; });
    // {1, 4, 9, 16, 25}
    
    // Filter: keep only even numbers
    auto evens = filter(numbers, [](int x) { return x % 2 == 0; });
    // {2, 4}
    
    // Reduce: sum all numbers
    auto sum = reduce(numbers, 0, [](int acc, int x) { return acc + x; });
    // 15
    
    return 0;
}

Function Composition

#include <functional>

template<typename F, typename G>
auto compose(F f, G g) {
    return [f, g](auto x) { return f(g(x)); };
}

int main() {
    auto add1 = [](int x) { return x + 1; };
    auto times2 = [](int x) { return x * 2; };
    
    auto composed = compose(times2, add1);
    // composed(x) = times2(add1(x)) = (x + 1) * 2
    
    std::cout << composed(5) << '\n';  // (5 + 1) * 2 = 12
    
    return 0;
}

Currying

auto curry(auto func) {
    return [func](auto first) {
        return [func, first](auto second) {
            return func(first, second);
        };
    };
}

int main() {
    auto add = [](int a, int b) { return a + b; };
    auto curried_add = curry(add);
    
    auto add5 = curried_add(5);
    
    std::cout << add5(3) << '\n';   // 8
    std::cout << add5(10) << '\n';  // 15
    
    return 0;
}

Performance Considerations

Lambda vs Function Pointer

// Function pointer (no inline)
void process1(const std::vector<int>& vec, int(*func)(int)) {
    for (int x : vec) {
        func(x);  // Indirect call, can't inline
    }
}

// Lambda (can inline)
template<typename F>
void process2(const std::vector<int>& vec, F func) {
    for (int x : vec) {
        func(x);  // Can be inlined by compiler
    }
}

int main() {
    std::vector<int> vec(1000000);
    
    auto lambda = [](int x) { return x * 2; };
    
    process1(vec, [](int x) { return x * 2; });  // Slower
    process2(vec, lambda);                        // Faster (inlining)
    
    return 0;
}

Capture Overhead

┌───────────────────────────────────────────────────────┐
│              Lambda Capture Overhead                  │
├───────────────────────────────────────────────────────┤
│ Capture Type     │ Size        │ Performance          │
├──────────────────┼─────────────┼──────────────────────┤
│ No capture       │ 1 byte      │ Best (empty)         │
│ By value (int)   │ sizeof(int) │ Good (inline)        │
│ By reference     │ sizeof(ptr) │ Good (indirect)      │
│ By value (large) │ sizeof(T)   │ Slow (copy)          │
│ [=] many vars    │ Sum of all  │ Depends on size      │
└───────────────────────────────────────────────────────┘

Tip: Capture only what you need!

Common Pitfalls

1. Dangling References

// BAD: Returning lambda with reference to local
auto make_lambda() {
    int x = 42;
    return [&x]() { return x; };  // DANGER: x destroyed!
}

// GOOD: Capture by value
auto make_lambda_safe() {
    int x = 42;
    return [x]() { return x; };  // OK: x copied
}

2. Capturing this

class Widget {
    int value_ = 42;
    
public:
    auto make_lambda_bad() {
        return [=]() {
            return value_;  // Captures 'this', not 'value_'!
        };
    }
    
    auto make_lambda_good() {
        return [*this]() {  // C++17: Capture by value
            return value_;
        };
    }
};

3. Capture in Loops

// BAD: All lambdas capture same reference
std::vector<std::function<int()>> funcs;
for (int i = 0; i < 5; ++i) {
    funcs.push_back([&i]() { return i; });  // All capture same i!
}
// All funcs return 5!

// GOOD: Capture by value
std::vector<std::function<int()>> funcs_good;
for (int i = 0; i < 5; ++i) {
    funcs_good.push_back([i]() { return i; });  // Each captures own i
}

Best Practices

1. Prefer Generic Lambdas

// GOOD: Works with any type
auto print = [](const auto& x) {
    std::cout << x << '\n';
};

// Less flexible
auto print_int = [](int x) {
    std::cout << x << '\n';
};

2. Use Init-Capture for Move-Only Types

auto ptr = std::make_unique<int>(42);

// GOOD: Move into lambda
auto lambda = [p = std::move(ptr)]() {
    return *p;
};

3. Avoid Capturing Everything

// BAD: Unclear what's captured
auto lambda = [=]() { /* ... */ };

// GOOD: Explicit captures
auto lambda = [x, y]() { /* ... */ };

4. Use mutable Sparingly

// Consider if you really need mutable state
// Often better to use references or return new values

C++23 Features

Explicit Object Parameter

// C++23: Explicit 'this' parameter
auto lambda = [](this auto self, int n) {
    if (n <= 0) return 1;
    return n * self(n - 1);  // Recursive without std::function!
};

std::cout << lambda(5) << '\n';  // 120

Summary

┌────────────────────────────────────────────────────────┐
│              Lambda Capabilities                       │
├────────────────────────────────────────────────────────┤
│ C++11: Basic lambdas, captures                         │
│ C++14: Generic lambdas (auto), init-capture            │
│ C++17: constexpr, *this capture                        │
│ C++20: Template parameters, concepts                   │
│ C++23: Explicit object parameter                       │
│                                                        │
│ Use Cases:                                             │
│ • STL algorithms predicates                            │
│ • Event handlers/callbacks                             │
│ • Custom comparators                                   │
│ • Functional programming                               │
│ • Inline helper functions                              │
└────────────────────────────────────────────────────────┘

Complete Practical Example: Event Processing System

Here’s a comprehensive example integrating lambdas, functional programming, and STL algorithms:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <map>
#include <chrono>
#include <memory>

// Event types
enum class EventType { Click, KeyPress, MouseMove, Scroll, Custom };

struct Event {
    EventType type;
    std::string target;
    std::chrono::system_clock::time_point timestamp;
    std::map<std::string, std::string> data;
    
    Event(EventType t, std::string tgt)
        : type(t), target(std::move(tgt))
        , timestamp(std::chrono::system_clock::now()) {}
};

class EventProcessor {
private:
    using EventHandler = std::function<void(const Event&)>;
    using EventFilter = std::function<bool(const Event&)>;
    using EventTransform = std::function<Event(Event)>;
    
    std::vector<Event> events_;
    std::map<EventType, std::vector<EventHandler>> handlers_;
    std::vector<EventFilter> filters_;
    
public:
    // Register event handler using lambda
    void on(EventType type, EventHandler handler) {
        handlers_[type].push_back(std::move(handler));
    }
    
    // Add filter (events must pass all filters)
    void add_filter(EventFilter filter) {
        filters_.push_back(std::move(filter));
    }
    
    // Dispatch event to handlers
    void dispatch(const Event& event) {
        // Check all filters using std::all_of with lambda
        bool should_process = std::all_of(filters_.begin(), filters_.end(),
            [&event](const EventFilter& filter) {
                return filter(event);
            });
        
        if (!should_process) {
            std::cout << "Event filtered out\n";
            return;
        }
        
        events_.push_back(event);
        
        // Find and execute handlers
        auto it = handlers_.find(event.type);
        if (it != handlers_.end()) {
            // Call each handler with lambda
            std::for_each(it->second.begin(), it->second.end(),
                [&event](const EventHandler& handler) {
                    handler(event);
                });
        }
    }
    
    // Get events filtered by predicate (lambda)
    std::vector<Event> get_events(EventFilter predicate) const {
        std::vector<Event> result;
        std::copy_if(events_.begin(), events_.end(),
                    std::back_inserter(result),
                    predicate);
        return result;
    }
    
    // Transform events using lambda
    std::vector<std::string> get_event_descriptions() const {
        std::vector<std::string> descriptions;
        std::transform(events_.begin(), events_.end(),
                      std::back_inserter(descriptions),
                      [](const Event& e) {
                          return "Event on: " + e.target;
                      });
        return descriptions;
    }
    
    // Functional pipeline with lambdas
    auto create_event_pipeline() {
        // Return a composed function
        return [this](EventType type, std::string target) {
            Event event{type, target};
            
            // Pipeline: validate -> transform -> dispatch
            auto validate = [](const Event& e) {
                return !e.target.empty();
            };
            
            auto transform = [](Event e) {
                e.data["processed"] = "true";
                return e;
            };
            
            if (validate(event)) {
                event = transform(event);
                dispatch(event);
                return true;
            }
            return false;
        };
    }
    
    // Reducer pattern with lambda
    int count_events_by_type(EventType type) const {
        return std::count_if(events_.begin(), events_.end(),
            [type](const Event& e) {
                return e.type == type;
            });
    }
    
    // Map-reduce with lambdas
    std::map<EventType, int> get_event_counts() const {
        std::map<EventType, int> counts;
        
        // Custom reduce operation
        std::for_each(events_.begin(), events_.end(),
            [&counts](const Event& e) {
                counts[e.type]++;
            });
        
        return counts;
    }
    
    // Higher-order function: returns a function
    auto create_throttled_handler(EventHandler handler, int max_calls) {
        // Capture by value (copy), mutable to modify counter
        return [handler, max_calls, counter = 0](const Event& e) mutable {
            if (counter < max_calls) {
                handler(e);
                counter++;
            } else {
                std::cout << "Handler throttled (max " << max_calls << " calls)\n";
            }
        };
    }
    
    // Debounce with lambda and timing
    auto create_debounced_handler(EventHandler handler, 
                                  std::chrono::milliseconds delay) {
        // Use shared_ptr for shared state across lambda copies
        auto last_call = std::make_shared<
            std::chrono::system_clock::time_point
        >(std::chrono::system_clock::now() - delay);
        
        return [handler, delay, last_call](const Event& e) {
            auto now = std::chrono::system_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
                now - *last_call
            );
            
            if (elapsed >= delay) {
                handler(e);
                *last_call = now;
            } else {
                std::cout << "Event debounced\n";
            }
        };
    }
    
    // Compose multiple handlers
    EventHandler compose_handlers(std::vector<EventHandler> handlers) {
        return [handlers = std::move(handlers)](const Event& e) {
            for (const auto& handler : handlers) {
                handler(e);
            }
        };
    }
    
    void show_statistics() const {
        std::cout << "\n=== Event Statistics ===\n";
        std::cout << "Total events: " << events_.size() << "\n";
        
        auto counts = get_event_counts();
        for (const auto& [type, count] : counts) {
            std::cout << "Type " << static_cast<int>(type) 
                      << ": " << count << " events\n";
        }
    }
};

int main() {
    EventProcessor processor;
    
    // 1. Basic lambda as event handler
    processor.on(EventType::Click, [](const Event& e) {
        std::cout << "Click handler: " << e.target << "\n";
    });
    
    // 2. Lambda with capture
    int click_count = 0;
    processor.on(EventType::Click, [&click_count](const Event& e) {
        click_count++;
        std::cout << "  Total clicks: " << click_count << "\n";
    });
    
    // 3. Generic lambda (C++14)
    auto log_event = [](const auto& event) {
        std::cout << "Logged: Event on " << event.target << "\n";
    };
    processor.on(EventType::KeyPress, [log_event](const Event& e) {
        log_event(e);
    });
    
    // 4. Add filter using lambda
    processor.add_filter([](const Event& e) {
        // Only process events for "button" elements
        return e.target.find("button") != std::string::npos ||
               e.target.find("key") != std::string::npos;
    });
    
    // 5. Throttled handler (max 2 calls)
    auto throttled = processor.create_throttled_handler(
        [](const Event& e) {
            std::cout << "Throttled handler executed: " << e.target << "\n";
        },
        2
    );
    processor.on(EventType::MouseMove, throttled);
    
    // 6. Debounced handler (100ms delay)
    auto debounced = processor.create_debounced_handler(
        [](const Event& e) {
            std::cout << "Debounced handler executed: " << e.target << "\n";
        },
        std::chrono::milliseconds(100)
    );
    processor.on(EventType::Scroll, debounced);
    
    // 7. Compose multiple handlers
    auto composed = processor.compose_handlers({
        [](const Event& e) { std::cout << "  Handler 1\n"; },
        [](const Event& e) { std::cout << "  Handler 2\n"; },
        [](const Event& e) { std::cout << "  Handler 3\n"; }
    });
    processor.on(EventType::Custom, composed);
    
    // Dispatch events
    std::cout << "=== Dispatching Events ===\n\n";
    
    processor.dispatch({EventType::Click, "button_submit"});
    processor.dispatch({EventType::Click, "button_cancel"});
    processor.dispatch({EventType::KeyPress, "key_enter"});
    processor.dispatch({EventType::MouseMove, "div_header"});  // Filtered
    processor.dispatch({EventType::MouseMove, "button_close"}); // Works
    processor.dispatch({EventType::Scroll, "button_scroll"});
    processor.dispatch({EventType::Custom, "button_custom"});
    
    // Use functional pipeline
    std::cout << "\n=== Event Pipeline ===\n";
    auto pipeline = processor.create_event_pipeline();
    pipeline(EventType::Click, "button_pipeline");
    pipeline(EventType::Click, "");  // Fails validation
    
    // Query events with lambda predicates
    std::cout << "\n=== Querying Events ===\n";
    auto click_events = processor.get_events([](const Event& e) {
        return e.type == EventType::Click;
    });
    std::cout << "Found " << click_events.size() << " click events\n";
    
    // Show statistics
    processor.show_statistics();
    
    // Demonstrate lazy evaluation with lambda
    std::cout << "\n=== Lazy Evaluation ===\n";
    auto expensive_computation = [](int x) {
        std::cout << "  Computing for " << x << "...\n";
        return x * x;
    };
    
    // Only computed when needed
    auto lazy_value = [expensive_computation, x = 5]() {
        return expensive_computation(x);
    };
    
    std::cout << "Lambda created but not executed yet\n";
    std::cout << "Now executing: " << lazy_value() << "\n";
    
    // Demonstrate memoization with lambda
    std::cout << "\n=== Memoization ===\n";
    auto fibonacci_memo = [cache = std::map<int, int>{}](int n) mutable {
        if (n <= 1) return n;
        
        if (cache.find(n) != cache.end()) {
            std::cout << "  Cache hit for " << n << "\n";
            return cache[n];
        }
        
        std::cout << "  Computing fibonacci(" << n << ")\n";
        // Simplified - real recursion would need Y-combinator
        cache[n] = n;  // Placeholder
        return cache[n];
    };
    
    fibonacci_memo(5);
    fibonacci_memo(5);  // Cache hit
    
    return 0;
}

Output:

=== Dispatching Events ===

Click handler: button_submit
  Total clicks: 1
Logged: Event on button_submit
Click handler: button_cancel
  Total clicks: 2
Logged: Event on button_cancel
Logged: Event on key_enter
Event filtered out
Throttled handler executed: button_close
Debounced handler executed: button_scroll
  Handler 1
  Handler 2
  Handler 3

=== Event Pipeline ===
Click handler: button_pipeline
  Total clicks: 3

=== Querying Events ===
Found 3 click events

=== Event Statistics ===
Total events: 6
Type 0: 3 events
Type 1: 1 events
Type 3: 1 events
Type 4: 1 events

=== Lazy Evaluation ===
Lambda created but not executed yet
Now executing:   Computing for 5...
25

=== Memoization ===
  Computing fibonacci(5)
  Cache hit for 5

Concepts Demonstrated:

This example shows how lambdas enable elegant functional programming in C++!


Next Steps


Part 10 of 22 - Lambdas and Functional Programming