11 Lambdas & Modern Control Flow

Overview: Inline Abstractions and Type Inference

Modern C++ (C++11 and beyond) introduces Lambda Expressions and type inference via auto. This note covers lambda functor mechanics, capture scopes, and range-based loops.


1. Lambda Expressions & Under-the-Hood Functors

A Lambda Expression allows you to define an anonymous function inline at its call site.

auto myLambda = [](int x, int y) { return x + y; };

How the Compiler translates Lambdas:

A lambda is syntactic sugar for a Functor (a class with an overloaded function call operator operator()). For the lambda above, the compiler generates a unique, unnamed struct behind the scenes:

struct UnnamedLambda {
    auto operator()(int x, int y) const { return x + y; }
};
UnnamedLambda myLambda; // Created at compile time

2. Capture Lists Policies

The capture list [] allows you to make variables from the enclosing outer scope available inside the lambda’s block.

  • Value Capture [x]: Copies the value of x into a member variable of the compiler-generated struct. Modifying x inside the lambda does not change the outer variable.
  • Reference Capture [&x]: Stores a reference to x inside the struct. Modifying x inside the lambda changes the outer variable.
  • Scope Value Capture [=]: Captures all variables currently in scope by value.
  • Scope Reference Capture [&]: Captures all variables currently in scope by reference.

3. Compile-time Type Inference: auto

The auto keyword tells the compiler to automatically deduce a variable’s type from its initialization expression at compile time.

auto val = 4.2; // Deduces to double
auto it = myVector.begin(); // Replaces long, complex iterator signatures

Static Typing remains intact:

  • Zero Runtime Overhead: Type deduction occurs entirely at compile time. The variable is strictly typed in the compiled machine code.
  • auto does not mean dynamic typing (like Python or JavaScript). You cannot reassign an auto variable to a value of a different type after declaration.

4. Range-based For Loops

Range-based loops simplify container traversal.

for (auto elem : arr) { ... }

Compilation translation:

The compiler translates a range-for loop into a traditional iterator-based loop:

for (auto it = arr.begin(); it != arr.end(); ++it) {
    auto elem = *it;
    ...
}

Avoid Unnecessary Copies

By default, auto elem copies each item in the container. When iterating over large structures, use const auto& elem to pass elements by reference without copying.


💻 Conceptual Code Demonstration

#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {10, 5, 20, 15};
    int multiplier = 3;
 
    // 1. Lambda capturing by value [multiplier]
    // The compiler copies 'multiplier' into the generated functor struct.
    auto multiplyByVal = [multiplier](int x) {
        return x * multiplier;
    };
 
    std::cout << "3 * 10 = " << multiplyByVal(10) << '\n';
 
    // 2. Pass Lambda to STL algorithm
    // Sorts elements in descending order
    std::sort(numbers.begin(), numbers.end(), [](int lhs, int rhs) {
        return lhs > rhs;
    });
 
    // 3. Range-based For Loop using const auto& reference (avoids copies)
    std::cout << "Sorted Numbers: ";
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    std::cout << '\n';
 
    return 0;
}