C++ Lambdas: Closures, Captures, Generic Lambdas, std::function vs auto & Functional Patterns (C++23)

C++ Lambdas: Closures, Captures, Generic Lambdas, std::function vs auto & Functional Patterns (C++23)
Table of Contents
- Lambda Anatomy: The Complete Syntax
- Capture Semantics: Value, Reference, and Init Captures
- Capture by Move: unique_ptr in Lambdas
- Generic Lambdas: auto and Template Parameters
- Mutable Lambdas: Modifying Captured Variables
- Lambdas in STL Algorithms
- Immediately Invoked Lambda Expressions (IIFE)
- std::function vs auto: The Performance Tradeoff
- Recursive Lambdas: Y-Combinator and deducing this (C++23)
- constexpr Lambdas (C++17) and consteval (C++20)
- C++23: explicit object parameter (deducing this)
- Frequently Asked Questions
- Key Takeaway
Lambda Anatomy: The Complete Syntax
Capture Semantics: Value, Reference, and Init Captures
Capture by Move: unique_ptr in Lambdas
unique_ptr can't be copied, so you can't use [=] to capture it. Use an init capture to move it:
Generic Lambdas: auto and Template Parameters
C++14 generic lambdas use auto parameters — each unique argument type generates a separate template instantiation:
Mutable Lambdas: Modifying Captured Variables
By default, captures by value create const copies inside the lambda body. mutable removes this restriction:
Lambdas in STL Algorithms
Lambdas replace hand-written functors and function objects for STL algorithms:
Immediately Invoked Lambda Expressions (IIFE)
An IIFE executes a lambda immediately — useful for complex initialization of const variables:
std::function vs auto: The Performance Tradeoff
Recursive Lambdas: Y-Combinator and deducing this (C++23)
C++ lambdas cannot directly call themselves because they have no name inside their own body. Solutions:
Frequently Asked Questions
Is a lambda always faster than std::function?
When passed directly to a template function (like std::sort), yes — the compiler knows the exact type and inlines the lambda body. When stored in std::function, lambda calls involve type erasure overhead (~3-5ns per call). For callbacks stored in containers, std::function is unavoidable. For algorithms, always pass lambdas directly.
Can lambdas be constexpr?
Yes — from C++17, lambdas are implicitly constexpr if their body is valid as a constant expression. Lambda IIFE is a common pattern for initializing constexpr arrays with complex logic. C++20 allows lambdas in unevaluated contexts, template arguments, and more.
What's the capture overhead?
Captured values are stored as members of the compiler-generated closure type. Captured by-value: space for each captured variable. Captured by-reference: one pointer per reference. Capturing [=] only captures variables actually used in the body — no overhead for unused variables. The lambda object itself lives on the stack unless stored in a std::function (may heap-allocate large closures).
Key Takeaway
Lambdas transformed C++ from a language where "passing logic" required separate functor classes to one where inline, composable, closures are first-class. The key discipline: pass to algorithms directly (zero overhead via auto), store in std::function only when necessary (accepts type erasure overhead), and use C++23's "deducing this" for recursive lambdas without overhead. Together with std::ranges, lambdas enable functional pipeline programming that remains fully optimizable by the compiler.
Read next: Multithreading & Atomics: High-Performance Concurrency →
Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.
