Mastering C++ Lambda Expressions in 3 Minutes: A Detailed Guide

#CPP #lambda #lambda expressions #Qt

Mastering C++ Lambda Expressions in 3 Minutes: A Detailed Guide

The C++ lambda expression (anonymous function) is an important feature introduced in C++11, allowing the definition of temporary, anonymous function objects within the code. It is primarily used to simplify code, especially suitable as callback functions for algorithms or for encapsulating small functional logic.

1. Basic Syntax

The core structure of a lambda expression is as follows:

[capture-list](parameters) mutable -> return-type {    // function body}

The meanings of each part are as follows:

capture-list:

Specifies how external variables are accessed within the lambda (e.g., by value capture, by reference capture). An empty list means no variables are captured.

parameters:

Similar to the parameter list of a regular function, it can be omitted (when there are no parameters).

mutable (optional):

Allows modification of variables captured by value within the lambda (by default, captured variables are const).

-> return-type (optional):

Explicitly specifies the return type. If the function body contains only one return statement, it can be omitted (the compiler will deduce it automatically).

function body: The specific logic implementation.

2. Key Features and Usage

1. Capture List (Core Feature)

The capture list controls the access permissions of the lambda to external variables, with common forms:

[]: Does not capture any external variables.

[var]: Captures variable var by value (copies it into the lambda, modifications do not affect the external variable).

[&var]: Captures variable var by reference (references the external variable, modifications will affect the external variable).

[=]: Captures all used external variables by value.

[&]: Captures all used external variables by reference.

[=, &var]: Captures all except var by reference, which is captured by value.

[&, var]: Captures all except var by value, which is captured by reference.

Example:

int a = 10, b = 20; // Capture a by value, b by reference auto func = [a, &b]() {    // a = 30; Error: variables captured by value cannot be modified (unless mutable is added)    b = 30; // Correct: can modify by reference    return a + b; }; func(); // After execution, b becomes 30, a remains 10

2. Parameters and Return Values

The parameter list is similar to that of a regular function, supporting type deduction (from C++14 onwards), default parameters, etc.

The return type can be omitted, and the compiler will deduce it based on the return statement (if the function body has no return, it defaults to void).

Example:

// No parameters, return type automatically deduced as int auto add = []() { return 1 + 2; }; // With parameters, explicitly specify return type (can be omitted here) auto multiply = [](int x, int y) -> int { return x * y; }; // C++14: parameter types automatically deduced auto sum = [](auto x, auto y) { return x + y; }; sum(3, 4.5); // Correct, deduced as double

3. The mutable Keyword

By default, variables captured by value are const within the lambda and cannot be modified. The mutable keyword can cancel this restriction (but modifications still do not affect the external variable):

int x = 5; auto func = [x]() mutable {    x = 10; // Allowed modification (only internal copy)    cout << x; // Outputs 10 }; func(); cout << x; // Still outputs 5 (external variable unchanged)

3. Usage Scenarios

As callback functions for algorithms combined with STL algorithms (such as sort, for_each), simplifying code:

vector<int> nums = {3, 1, 4, 1, 5}; // Use lambda to specify sorting rule (descending) sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; });

Encapsulating small logic to replace named functions used only once, reducing code redundancy:

// Temporarily define a print function auto print = [](const string& s) { cout &lt;&lt; "Message: " &lt;&lt; s &lt;&lt; endl; }; print("Hello lambda");

Capturing context variables for easy access to external variables without passing them as parameters:

int threshold = 10; vector<int> nums = {5, 15, 3, 20}; // Filter elements greater than threshold vector<int> res; for_each(nums.begin(), nums.end(), [&res, threshold](int n) {    if (n > threshold) res.push_back(n); });

4. Essence and Cautions

Essence: The lambda expression is converted by the compiler into an anonymous function object, and the captured variables become member variables of that object.

Lifecycle: The lambda expression itself is a temporary object. If you need to extend its lifecycle (e.g., store it in a variable), you need to receive it with auto.

Risks of Capturing References: If the lifecycle of the lambda exceeds that of the referenced variable, it will lead to dangling references (e.g., capturing a reference to a local variable and calling the lambda after the variable is destroyed).

Conversion with Function Pointers: A lambda without captures can be converted to a function pointer, while one with captures cannot.

Conclusion

The core value of lambda expressions is to simplify code and enhance readability, especially suitable for short, one-time function logic. By flexibly accessing external variables through the capture list and combining with STL algorithms, coding efficiency can be significantly improved, but attention must be paid to the lifecycle issues of captured references.

Thank you for reading! If you find this helpful, please give a thumbs up and follow us!!!

Leave a Comment