C++Functional Programming

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

TT
TopicTrick Team
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

mermaid
cpp

Capture Semantics: Value, Reference, and Init Captures

cpp

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:

cpp

Generic Lambdas: auto and Template Parameters

C++14 generic lambdas use auto parameters — each unique argument type generates a separate template instantiation:

cpp

Mutable Lambdas: Modifying Captured Variables

By default, captures by value create const copies inside the lambda body. mutable removes this restriction:

cpp

Lambdas in STL Algorithms

Lambdas replace hand-written functors and function objects for STL algorithms:

cpp

Immediately Invoked Lambda Expressions (IIFE)

An IIFE executes a lambda immediately — useful for complex initialization of const variables:

cpp

std::function vs auto: The Performance Tradeoff

cpp

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:

cpp

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.