Intermediate 12 min

Lambda Expressions

Master lambda expressions for creating inline anonymous functions with captures for powerful, concise code

Learn how to create inline anonymous functions using lambda expressions - one of the most powerful features introduced in C++11.

A Simple Example

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

int main() {
    std::vector<int> numbers{5, 2, 8, 1, 9, 3};

    // Simple lambda - no captures, no parameters
    auto printHello = []() {
        std::cout << "Hello from lambda!" << "\n";
    };
    printHello();

    // Lambda with parameters
    auto square = [](int x) {
        return x * x;
    };
    std::cout << "Square of 5: " << square(5) << "\n";

    // Lambda with STL algorithm
    std::sort(numbers.begin(), numbers.end(),
              [](int a, int b) { return a > b; });  // Descending order

    // Lambda with auto return type
    auto add = [](int a, int b) { return a + b; };  // return type deduced

    // Explicit return type
    auto divide = [](double a, double b) -> double {
        return a / b;
    };

    // Using lambda inline
    int count = std::count_if(numbers.begin(), numbers.end(),
                               [](int n) { return n > 5; });
    std::cout << "Numbers > 5: " << count << "\n";

    return 0;
}

Breaking It Down

Lambda Syntax: [](){} Structure

  • What it does: Creates an anonymous function right where you need it
  • [] - Capture clause: Specifies which variables from surrounding scope to use
  • () - Parameter list: Just like a regular function
  • {} - Function body: The code to execute
  • Remember: Think of it as [capture](parameters){body}

Capture Clauses

  • [] - Captures nothing (most common for simple operations)
  • [x] - Captures x by value (makes a copy)
  • [&x] - Captures x by reference (uses the original)
  • [=] - Captures all by value (convenient but dangerous)
  • [&] - Captures all by reference (even more dangerous)
  • [x, &y] - Mix and match: x by value, y by reference
  • Remember: Be explicit about what you capture to avoid bugs

Lambda with STL Algorithms

  • What it does: Makes STL algorithms incredibly powerful and readable
  • Before lambdas: Had to write separate named functions or function objects
  • With lambdas: Write the logic inline where you use it
  • Example: std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
  • Remember: This is where lambdas truly shine

Return Type Deduction

  • What it does: Compiler automatically figures out the return type
  • Auto deduction: [](int x) { return x * 2; } - returns int automatically
  • Explicit type: [](int x) -> double { return x * 2.0; } - specify when needed
  • Use explicit when: Multiple return paths with different types
  • Remember: Most of the time auto deduction works perfectly

Why This Matters

  • Lambdas changed C++ forever. Before C++11, using STL algorithms required writing separate functions or heavy function objects.
  • Lambdas let you write the function right where you use it, capturing local variables naturally.
  • They are essential for modern C++, async programming, and functional-style code.

Critical Insight

Lambda captures create a closure - a function that "remembers" the environment where it was created. [x] makes a copy that stays with the lambda forever. [&x] creates a reference that must stay valid. [=] and [&] are convenient but potentially dangerous - they capture everything in scope, which can lead to accidental captures. Be explicit when you can!

Also, lambdas are not function pointers - each lambda has a unique compiler-generated type. They can convert to function pointers only if they have no captures.

Best Practices

Be explicit with captures: Use [x, y] instead of [=] to clearly show which variables are captured and avoid accidental captures.

Use const references for large objects: [&vec] instead of [vec] avoids copying large containers when you only need to read them.

Prefer lambdas over function objects: For simple operations with STL algorithms, lambdas are more readable than separate function classes.

Use auto for lambda variables: Store lambdas in auto variables since each lambda has a unique compiler-generated type.

Common Mistakes

Dangling references: Capturing by reference [&x] when lambda outlives x leads to undefined behavior. The reference points to destroyed memory.

Accidental captures with [=] or [&]: Capturing everything can include unintended variables, leading to subtle bugs.

Modifying captured values: Values captured by [x] are const by default. Use [x]() mutable {} to modify copies.

Assuming lambdas are function pointers: Each lambda has a unique type. Only capture-less lambdas convert to function pointers.

Debug Challenge

This lambda tries to capture a local variable but the syntax is wrong. Click the highlighted line to fix it:

1 #include <iostream>
2
3 int main() {
4 int multiplier{10};
5 auto multiply = (int x) { return x * multiplier; };
6 std::cout << multiply(5) << "\n";
7 return 0;
8 }

Quick Quiz

  1. What is the difference between [x] and [&x]?
[x] captures by value (copy), [&x] captures by reference
No difference
[x] is faster
  1. Can a lambda with captures be converted to a function pointer?
Yes, always
Only if it has no captures
No, never
  1. What does [=] capture in a lambda?
All local variables by reference
Nothing
All local variables by value (copy)

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once