C++ Lambdas: Syntax, Captures, mutable, and Generic Lambdas

C++ Lambdas: Syntax, Captures, mutable, and Generic Lambdas

이 글의 핵심

Hands-on guide to C++ lambdas—syntax, captures, mutable, and patterns for algorithms and callbacks.

Basic grammar

Lambdas are useful when you want to put short function logic in one place for STL algorithms and asynchronous callbacks.In this article, you can learn how to select the capture list and return type by following the grammar and capture examples in the following sections in order.

// Basic form
auto lambda = [] {
    cout << "Hello Lambda!" << endl;
};

lambda();  // call

// Parameters and return type
auto add = [](int a, int b) -> int {
    return a + b;
};

cout << add(3, 5) << endl;  // 8

// Deduced return type
auto multiply = [](int a, int b) {
    return a * b;  // int
};

Capture

Value Capture

int x = 10;
int y = 20;

// capture by value
auto lambda1 = [x, y]() {
cout << x + y << endl;
};

// Capture all variables by value
auto lambda2 = [=]() {
cout << x + y << endl;
};

Reference Capture

int count = 0;

// capture by reference
auto increment = [&count]() {
count++;
};

increment();
cout << count << endl;  // 1

// Capture all variables by reference
auto lambda = [&]() {
count++;
};

Mixed Capture

int x = 10;
int y = 20;

// x is the value, y is the reference
auto lambda = [x, &y]() {
y = x + 5;
};

lambda();
cout << y << endl;// 15

mutable lambda

int x = 10;

// value capture is const by default
auto lambda1 = [x]() {
//x++;// error!const
};

// Can be modified with mutable
auto lambda2 = [x]() mutable {
x++;// OK (edit copy)
cout << x << endl;
};

lambda2();// 11
cout << x << endl;// 10 (original unchanged)

Practical example

Example 1: STL algorithm and lambda

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// Keep only evens
auto it = remove_if(numbers.begin(), numbers.end(), [](int x) {
    return x % 2 != 0;
});
numbers.erase(it, numbers.end());

// Print
for_each(numbers.begin(), numbers.end(), [](int x) {
    cout << x << " ";
});
cout << endl;  // 2 4 6 8 10

// Sort descending
sort(numbers.begin(), numbers.end(), [](int a, int b) {
    return a > b;
});

// Check predicate
bool allEven = all_of(numbers.begin(), numbers.end(), [](int x) {
    return x % 2 == 0;
});
cout << "all even: " << allEven << endl;

return 0;
}

Description: Lambdas allow you to write simple logic inline without a separate function.

Example 2: Event handler

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

class Button {
private:
string label;
function<void()> onClick;

public:
Button(string l) : label(l) {}

void setOnClick(function<void()> handler) {
onClick = handler;
}

void click() {
cout << label << "Click!"<< endl;
if (onClick) {
onClick();
}
}
};

int main() {
int clickCount = 0;

Button btn1("Button1");
btn1.setOnClick([&clickCount]() {
clickCount++;
cout << "Click count: " << clickCount << endl;
});

Button btn2("Button2");
btn2.setOnClick([]{
    cout << "Button 2 was clicked!" << endl;
});

btn1.click();
btn1.click();
btn2.click();

return 0;
}

Description: You can register event handlers succinctly with lambdas.

Example 3: Generic lambda (C++14)

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
// Generic lambda (auto parameters)
auto print = [](auto x) {
    cout << x << endl;
};

print(10);       // int
print(3.14);     // double
print("Hello");  // const char*

auto add = [](auto a, auto b) {
    return a + b;
};

cout << add(1, 2) << endl;
cout << add(1.5, 2.5) << endl;
cout << add(string("Hello"), string(" World")) << endl;

auto printVector = [](const auto& vec) {
    for (const auto& item : vec) {
        cout << item << " ";
    }
    cout << endl;
};

vector<int> v1 = {1, 2, 3};
vector<string> v2 = {"a", "b", "c"};

printVector(v1);// 1 2 3
printVector(v2);// a b c

return 0;
}

Explanation: Starting with C++14, you can create a generic lambda with an auto parameter.

Frequently occurring problems

Problem 1: Dangling References

Symptom: Crash or strange values when running lambda

Cause: The reference-captured variable has already been destroyed.

Fix:

// ❌ Dangerous code
function<void()> makeCounter() {
int count = 0;
return [&count]() { // See dangling!
count++;
cout << count << endl;
};
}

auto counter = makeCounter();
counter();// crash or strange value

// ✅ Capture by value
function<void()> makeCounter() {
int count = 0;
return [count]() mutable {
count++;
cout << count << endl;
};
}

Problem 2: Capturing this

Symptom: Inability to access member variables

Cause: Missing capture of this

Solution:

class Counter {
private:
int count = 0;

public:
void increment() {
    // ❌ Error: cannot reference members without capture
    // auto bad = [] { count++; };

    // ✅ Capture this
    auto lambda2 = [this]() {
        count++;
    };

    // ✅ C++17: copy *this
    auto lambda3 = [*this]() mutable {
        count++;  // modifies copy
    };
}
};

Issue 3: Capture initialization (C++14)

Symptom: Complex initialization is not possible

Cause: Only simple capture is possible

Solution:

// ❌ Not possible in C++11
unique_ptr<int> ptr = make_unique<int>(10);
auto lambda = [ptr]() { // Error!Unable to copy unique_ptr
cout << *ptr << endl;
};

// ✅ C++14: Capture initialization
unique_ptr<int> ptr = make_unique<int>(10);
auto lambda = [ptr = move(ptr)]() {
cout << *ptr << endl;
};

FAQ

Q1: When do you use lambda?

A:

  • Predicates of STL algorithm
  • Event handler
  • Callback function
  • Simple one-off function

Q2: Lambda vs function pointer?

A: Lambdas are more flexible and captureable.Function pointers are converted only to lambdas without capture.

Q3: What is the type of lambda?

A: Each lambda has a unique type.Save it as auto or function<>.

Q4: What is a recursive lambda?

A: Available starting from C++14.

function<int(int)> factorial = [&factorial](int n) {
return n <= 1 ?1 : n * factorial(n - 1);
};

Q5: Does lambda affect performance?

A: When inlined, there is very little overhead.Same performance as a regular function.

Q6: What are the capture defaults?

A:

  • [=]: all as values (not recommended, capture explicitly)
  • [&]: all by reference (Caution: risk of dangling references)
  • []: No capture (most secure)

Good article to read together (internal link)

Here’s another article related to this topic.

  • C++ Lambda Capture |“Lambda Capture” complete guide
  • Complete guide to C++ lambda basics |Capture·mutable·generic lambda and practical patterns
  • C++ Init Capture |“Init Capture” Guide

Practical tips

These are tips that can be applied right away in practice.

Debugging tips

  • If you run into a problem, check the compiler warnings first.
  • Reproduce the problem with a simple test case

Performance Tips

  • Don’t optimize without profiling
  • Set measurable indicators first

Code review tips

  • Check in advance for areas that are frequently pointed out in code reviews.
  • Follow your team’s coding conventions

Practical checklist

This is what you need to check when applying this concept in practice.

Before writing code

  • Is this technique the best way to solve the current problem?
  • Can team members understand and maintain this code?
  • Does it meet the performance requirements?

Writing code

  • Have you resolved all compiler warnings?
  • Have you considered edge cases?
  • Is error handling appropriate?

When reviewing code

  • Is the intention of the code clear?
  • Are there enough test cases?
  • Is it documented?

Use this checklist to reduce mistakes and improve code quality.


Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, lambda, lambda, anonymous function, C++11, etc.


  • C++ std::function vs function pointer |
  • C++ lambda capture error |
  • C++ Lambda Capture |
  • C++ async & launch |
  • C++ Atomic Operations |