[C++] Thorough Explanation of Lambda Expressions | Captures and Generic Lambdas

目次

Introduction

When using standard algorithms like std::sort or std::find_if in C++, defining a separate function just to specify a condition can be tedious.

Lambda expressions, introduced in C++11, answer the need for “disposable, simple functions right on the spot.” Using lambda expressions allows you to write the processing logic directly inline where the function is defined.

In this article, I will explain everything from the basic syntax of lambda expressions, which dramatically simplify C++ code, to their powerful capture features.

1. Basic Syntax of Lambda Expressions

A lambda expression consists of the following elements:

[Capture](Parameter List) -> Return Type { Function Body }

  • [Capture]: Specifies how to access variables outside the lambda expression (explained later).

  • (Parameter List): Defines arguments just like a normal function.
  • -> Return Type: Explicitly states the return type. Can be omitted if the compiler can infer it.
  • { Function Body }: Describes the processing you want to execute.

Sample Code

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

using namespace std;

int main() {
    // 1. The simplest lambda expression
    auto greet = [](){ cout << "Hello, Lambda!" << endl; };
    greet();

    // 2. Lambda expression with arguments and return value
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    cout << "add(10, 20): " << add(10, 20) << endl;

    return 0;
}

2. Capture: Accessing Outer Variables

The most powerful feature of lambda expressions is “Capture.” By writing specifications inside [], you can import variables within the scope where the lambda is defined and use them inside the function body.

Sample Code

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

using namespace std;

int main() {
    vector<int> numbers = {1, 2, 3, 4, 5, 6};
    int threshold = 3;
    
    // 1. Value Capture [=] or [threshold]
    //    Import a "copy" of threshold into the lambda
    int count_gt_copy = count_if(numbers.begin(), numbers.end(),
        [threshold](int n){ return n > threshold; }
    );
    cout << "Count elements > 3 (Value Capture): " << count_gt_copy << endl;


    // 2. Reference Capture [&] or [&threshold]
    //    Import a "reference" to threshold into the lambda
    for_each(numbers.begin(), numbers.end(),
        [&](int& n){ n *= threshold; } // Rewrite original elements using the reference-captured threshold
    );
    
    cout << "After multiplying each element by 3: ";
    for(int n : numbers) cout << n << " ";
    cout << endl;
    
    return 0;
}

Explanation

CaptureDescription
[]Captures nothing.
[=]Captures all automatic variables by value (copy).
[&]Captures all automatic variables by reference.
[x, &y]Captures x by value and y by reference individually.

3. Generic Lambdas (C++14)

By using auto for the argument types, you can define template-like lambda expressions (Generic Lambdas) that can handle various types.

Sample Code

#include <iostream>
#include <string>

using namespace std;

int main() {
    // With the auto keyword, it can accept arguments of any type
    auto generic_add = [](auto a, auto b) {
        return a + b;
    };

    cout << generic_add(10, 20) << endl;       // int + int
    cout << generic_add(3.14, 2.71) << endl;   // double + double
    cout << generic_add(string("a"), string("b")) << endl; // string + string

    return 0;
}

4. Initialization Capture (C++14)

You can declare and initialize new variables inside the capture clause. This is particularly useful when you want to move ownership of a variable into the lambda using move.

Sample Code

#include <iostream>
#include <memory>
#include <utility> // for std::move

using namespace std;

int main() {
    auto p = make_unique<int>(100);

    // Move ownership of p to a new variable ptr inside the lambda
    auto lambda_with_move = [ptr = move(p)]() {
        cout << "Value inside lambda: " << *ptr << endl;
    };

    lambda_with_move();
    
    // Afterwards, p is empty (p == nullptr)
    if (!p) {
        cout << "p is now empty." << endl;
    }

    return 0;
}

Summary

In this article, I explained everything from the basics to the applications of C++ lambda expressions. Lambda expressions are active in every aspect of modern C++ programming, including combinations with STL algorithms and cooperation with std::function.

  • [](){} is the basic syntax.
  • Use Capture [] to access outer variables. Distinguish between value capture and reference capture.
  • Advanced features like Generic Lambdas and Initialization Captures exist.

Mastering lambda expressions will make your code more concise and expressive.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次