C++ でのラムダ式Lambda Expressions in C++

C++ 11 以降では、ラムダ式 (多くの場合、ラムダと呼ばれます) は、関数に引数として呼び出されるか、引数として渡される匿名関数オブジェクト (クロージャ) を定義する便利な方法です。In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it is invoked or passed as an argument to a function. 一般に、ラムダは、アルゴリズムまたは非同期のメソッドに渡される数行のコードをカプセル化するために使用されます。Typically lambdas are used to encapsulate a few lines of code that are passed to algorithms or asynchronous methods. ここでは、ラムダとはどのようなものかを定義して他のプログラミング手法と比較した上で、その利点を説明し、基本的な例を示します。This article defines what lambdas are, compares them to other programming techniques, describes their advantages, and provides a basic example.

ラムダ式のパーツParts of a Lambda Expression

ISO C++ 標準では、std::sort() 関数の 3 番目の引数として渡される単純なラムダを示しています。The ISO C++ Standard shows a simple lambda that is passed as the third argument to the std::sort() function:

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

次の図は、ラムダのパーツを示しています。This illustration shows the parts of a lambda:

ラムダ式の構造要素Structural elements of a lambda expression

  1. capture 句( C++仕様ではlambda-introducerとも呼ばれます)。capture clause (Also known as the lambda-introducer in the C++ specification.)

  2. パラメーターリストOptional.parameter list Optional. (ラムダ宣言子とも呼ばれます)(Also known as the lambda declarator)

  3. 変更可能な指定Optional.mutable specification Optional.

  4. 例外-指定Optional.exception-specification Optional.

  5. 末尾の戻り値の型Optional.trailing-return-type Optional.

  6. ラムダの本体lambda body.

capture 句Capture Clause

ラムダは、その本体に新しい変数を導入できます ( C++ 14の場合)。また、周囲のスコープから変数にアクセスしたり、変数をキャプチャしたりすることもできます。A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. ラムダは、キャプチャ句 (標準構文ではlambda-introducer ) で始まります。これは、キャプチャされる変数と、キャプチャが値渡しか参照渡しかを指定します。A lambda begins with the capture clause (lambda-introducer in the Standard syntax), which specifies which variables are captured, and whether the capture is by value or by reference. アンパサンド (&) プレフィックス付きの変数は参照でアクセスされ、アンパサンド プレフィックスなしの変数は値でアクセスされます。Variables that have the ampersand (&) prefix are accessed by reference and variables that do not have it are accessed by value.

空のキャプチャ句 [ ] は、ラムダ式の本体が外側のスコープ内の変数にアクセスしないことを示します。An empty capture clause, [ ], indicates that the body of the lambda expression accesses no variables in the enclosing scope.

既定のキャプチャモード (標準構文ではcapture ) を使用して、ラムダで参照されている外部変数をキャプチャする方法を示すことができます。 [&] は参照するすべての変数が参照によってキャプチャされることを意味し、[=] は値によってキャプチャされることを意味します。You can use the default capture mode (capture-default in the Standard syntax) to indicate how to capture any outside variables that are referenced in the lambda: [&] means all variables that you refer to are captured by reference, and [=] means they are captured by value. 既定のキャプチャ モードを使用してから、特定の変数には明示的に反対のモードを指定することができます。You can use a default capture mode, and then specify the opposite mode explicitly for specific variables. たとえば、ラムダ式の本体が参照によって外部変数 total にアクセスし、値によって外部変数 factor にアクセスする場合、次の capture 句は同じ結果になります。For example, if a lambda body accesses the external variable total by reference and the external variable factor by value, then the following capture clauses are equivalent:

[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

キャプチャの既定値が使用されると、ラムダに示されている変数のみがキャプチャされます。Only variables that are mentioned in the lambda are captured when a capture-default is used.

Capture 句に既定のキャプチャ &が含まれている場合、その capture 句の capture 内の identifier には & identifierの形式を使用できません。If a capture clause includes a capture-default &, then no identifier in a capture of that capture clause can have the form & identifier. 同様に、capture 句に既定のキャプチャ =が含まれている場合、その capture 句の capture には、= identifierという形式を使用できません。Likewise, if the capture clause includes a capture-default =, then no capture of that capture clause can have the form = identifier. 識別子またはthisを capture 句で複数回表示されることはできません。An identifier or this cannot appear more than once in a capture clause. 次のコードでは、そのいくつかの例を示しています。The following code snippet illustrates some examples.

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};      // OK
    [&, &i]{};     // ERROR: i preceded by & when & is the default
    [=, this]{};   // ERROR: this when = is the default
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

次の可変個引数テンプレートの例に示すように、キャプチャの後に省略記号を使用すると、パックの展開になります。A capture followed by an ellipsis is a pack expansion, as shown in this variadic template example:

template<class... Args>
void f(Args... args) {
    auto x = [args...] { return g(args...); };
    x();
}

クラスのメソッドの本体でラムダ式を使用するにはthis外側のクラスのメソッドとデータ メンバーにアクセスを提供する capture 句へのポインター。To use lambda expressions in the body of a class method, pass the this pointer to the capture clause to provide access to the methods and data members of the enclosing class.

Visual Studio 2017 バージョン15.3 以降( /std: c++ 17で利用可能): capture 句で *this を指定することにより、このポインターを値でキャプチャできます。Visual Studio 2017 version 15.3 and later (available with /std:c++17): The this pointer may be captured by value by specifying *this in the capture clause. Capture by value は、ラムダ式を encapulates する匿名関数オブジェクトであるクロージャ全体が、ラムダが呼び出されるすべての呼び出しサイトにコピーされることを意味します。Capture by value means that the entire closure, which is the anonymous function object that encapulates the lambda expression, is copied to every call site where the lambda is invoked. 値によるキャプチャは、ラムダが並列または非同期操作で実行される場合に便利です。特に、特定のハードウェアアーキテクチャ (NUMA など) で実行されます。Capture by value is useful when the lambda will execute in parallel or asynchronous operations, especially on certain hardware architectures such as NUMA.

クラスメソッドでラムダ式を使用する方法を示す例については、「ラムダ式の例」の「例: メソッドでのラムダ式の使用」を参照してください。For an example that shows how to use lambda expressions with class methods, see "Example: Using a Lambda Expression in a Method" in Examples of Lambda Expressions.

capture 句を使用するとき、特にマルチスレッドでラムダを使用するときは、次の重要点に注意することをお勧めします。When you use the capture clause, we recommend that you keep these points in mind, particularly when you use lambdas with multithreading:

  • 参照キャプチャは外部の変数を変更するために使用できますが、値キャプチャはその目的には使用できませんReference captures can be used to modify variables outside, but value captures cannot. (mutableを指定すると、コピーは変更できますが、元のものは変更できません)。(mutable allows copies to be modified, but not originals.)

  • 参照キャプチャでは更新が外部の変数に反映されますが、値キャプチャでは反映されません。Reference captures reflect updates to variables outside, but value captures do not.

  • 参照キャプチャは有効期間に依存しますが、値キャプチャは依存しません。Reference captures introduce a lifetime dependency, but value captures have no lifetime dependencies. これは、ラムダを非同期的に実行する場合は特に重要です。This is especially important when the lambda runs asynchronously. 非同期のラムダで参照によってローカルをキャプチャする場合は、そのローカルは非常に高い可能性でラムダが実行するまでになくなり、結果として実行時にアクセス違反となります。If you capture a local by reference in an async lambda, that local will very possibly be gone by the time the lambda runs, resulting in an access violation at run time.

汎用化されたキャプチャ (C++ 14)Generalized capture (C++ 14)

C++ 14 では、capture 句にある新しい変数がラムダ関数の外側のスコープに存在する必要なしに、この変数を導入し、初期化することができます。In C++14, you can introduce and initialize new variables in the capture clause, without the need to have those variables exist in the lambda function’s enclosing scope. 初期化は、任意の式として表現できます。新しい変数の型は、式によって生成される型から推測されます。The initialization can be expressed as any arbitrary expression; the type of the new variable is deduced from the type produced by the expression. この機能の利点の 1 つは、C++ 14 では移動のみの変数 (std::unique_ptr) を周辺のスコープからキャプチャして、ラムダで使用できることです。One benefit of this feature is that in C++14 you can capture move-only variables (such as std::unique_ptr) from the surrounding scope and use them in a lambda.

pNums = make_unique<vector<int>>(nums);
//...
      auto a = [ptr = move(pNums)]()
        {
           // use ptr
        };

パラメーター リストParameter List

変数をキャプチャするだけでなく、ラムダは入力パラメーターを受け入れることができます。In addition to capturing variables, a lambda can accept input parameters. パラメーターリスト (標準構文のラムダ宣言子) は省略可能であり、ほとんどの部分は関数のパラメーターリストに似ています。A parameter list (lambda declarator in the Standard syntax) is optional and in most aspects resembles the parameter list for a function.

auto y = [] (int first, int second)
{
    return first + second;
};

C++ 14では、パラメーターの型がジェネリックの場合は、型指定子として auto キーワードを使用できます。In C++ 14, if the parameter type is generic, you can use the auto keyword as the type specifier. これにより、コンパイラにテンプレートとして関数呼び出し演算子を作成するように指示します。This tells the compiler to create the function call operator as a template. パラメーター リストの auto の各インスタンスは、別個の型パラメーターと同等です。Each instance of auto in a parameter list is equivalent to a distinct type parameter.

auto y = [] (auto first, auto second)
{
    return first + second;
};

ラムダ式は、引数として別のラムダ式を受け取ることができます。A lambda expression can take another lambda expression as its argument. 詳細については、「ラムダ式の例」の「上位ラムダ式」を参照してください。For more information, see "Higher-Order Lambda Expressions" in the topic Examples of Lambda Expressions.

パラメーターリストは省略可能であるため、ラムダ式に引数を渡さず、ラムダ宣言子に例外の指定末尾の戻り値の型 、または変更可能な値が含まれていない場合は、空のかっこを省略できます。Because a parameter list is optional, you can omit the empty parentheses if you do not pass arguments to the lambda expression and its lambda-declarator does not contain exception-specification, trailing-return-type, or mutable.

変更可能な指定Mutable Specification

通常、ラムダの関数呼び出し演算子は値によって定数になりますが、 mutableキーワードを使用すると、この出力が取り消されます。変更可能なデータメンバーは生成されません。Typically, a lambda's function call operator is const-by-value, but use of the mutable keyword cancels this out. It does not produce mutable data members. mutable の指定により、ラムダ式の本体で値キャプチャされる変数を変更できるようになります。The mutable specification enables the body of a lambda expression to modify variables that are captured by value. この記事の後半の例では、変更可能なを使用する方法について説明します。Some of the examples later in this article show how to use mutable.

例外の指定Exception Specification

noexcept 例外の指定を使用して、ラムダ式が例外をスローしないことを示すことができます。You can use the noexcept exception specification to indicate that the lambda expression does not throw any exceptions. 通常の関数と同様に、 C++次に示すように、ラムダ式が noexcept 例外の指定を宣言し、ラムダ本体が例外をスローする場合は、Microsoft コンパイラによって警告C4297が生成されます。As with ordinary functions, the Microsoft C++ compiler generates warning C4297 if a lambda expression declares the noexcept exception specification and the lambda body throws an exception, as shown here:

// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
   []() noexcept { throw 5; }();
}

詳細については、「例外の指定 (throw)」を参照してください。For more information, see Exception Specifications (throw).

戻り値の型Return Type

ラムダ式の戻り値の型は自動的に推測されます。The return type of a lambda expression is automatically deduced. 末尾の戻り値の型を指定しない限り、 autoキーワードを使用する必要はありません。You don't have to use the auto keyword unless you specify a trailing-return-type. 末尾の戻り値の型は、通常のメソッドまたは関数の戻り値の型の部分に似ています。The trailing-return-type resembles the return-type part of an ordinary method or function. ただし、戻り値の型はパラメーター リストに従い、戻り値の型の前に trailing-return-type キーワード -> を含める必要があります。However, the return type must follow the parameter list, and you must include the trailing-return-type keyword -> before the return type.

ラムダ式の本体に return ステートメントが 1 つだけ含まれるか、ラムダ式が値を返さない場合は、ラムダ式の return-type 部分を省略できます。You can omit the return-type part of a lambda expression if the lambda body contains just one return statement or the expression does not return a value. ラムダの本体が単一の return ステートメントで構成される場合、コンパイラは return 式の型から戻り値の型を推測します。If the lambda body contains one return statement, the compiler deduces the return type from the type of the return expression. それ以外の場合、コンパイラは戻り値の型をvoidに推測します。Otherwise, the compiler deduces the return type to be void. この原理を説明する次のコード例について考えてみましょう。Consider the following example code snippets that illustrate this principle.

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing
                                  // return type from braced-init-list is not valid

ラムダ式は、戻り値として別のラムダ式を作成できます。A lambda expression can produce another lambda expression as its return value. 詳細については、「ラムダ式の例」の「上位ラムダ式」を参照してください。For more information, see "Higher-Order Lambda Expressions" in Examples of Lambda Expressions.

ラムダ式の本体Lambda Body

ラムダ式のラムダ本体 (標準構文の複合ステートメント) には、通常のメソッドまたは関数の本体に含めることができるすべてのものを含めることができます。The lambda body (compound-statement in the Standard syntax) of a lambda expression can contain anything that the body of an ordinary method or function can contain. 通常の関数の本体もラムダ式の本体も次の種類の変数にアクセスできます。The body of both an ordinary function and a lambda expression can access these kinds of variables:

  • 前に説明したように、外側のスコープから変数がキャプチャされました。Captured variables from the enclosing scope, as described previously.

  • パラメーターParameters

  • ローカル宣言変数Locally-declared variables

  • クラス内で宣言されている場合、データ メンバーをクラスおよびthisがキャプチャClass data members, when declared inside a class and this is captured

  • 静的ストレージ存続期間の任意の変数 (たとえば、グローバル変数)Any variable that has static storage duration—for example, global variables

次の例には、変数 n を明示的に値でキャプチャし、変数 m を暗黙的に参照でキャプチャするラムダ式が含まれています。The following example contains a lambda expression that explicitly captures the variable n by value and implicitly captures the variable m by reference:

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}
5
0

変数 n は値でキャプチャされるため、ラムダ式への呼び出しの後も値は 0 のまま残ります。Because the variable n is captured by value, its value remains 0 after the call to the lambda expression. 変更可能な指定により、ラムダ内で n を変更できます。The mutable specification allows n to be modified within the lambda.

ラムダ式は自動ストレージ存続期間がある変数のみキャプチャできますが、ラムダ式の本体では静的ストレージ存続期間がある変数を使用できます。Although a lambda expression can only capture variables that have automatic storage duration, you can use variables that have static storage duration in the body of a lambda expression. 次の例では、generate 関数とラムダ式を使用して、vector オブジェクトの各要素に値を代入します。The following example uses the generate function and a lambda expression to assign a value to each element in a vector object. ラムダ式は、静的変数を変更して次の要素の値を生成します。The lambda expression modifies the static variable to generate the value of the next element.

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this is not thread-safe and is shown for illustration only
}

詳細については、「 generate」を参照してください。For more information, see generate.

次のコード例では、前の例の関数を使用して、 C++標準ライブラリアルゴリズム generate_nを使用するラムダ式の例を追加します。The following code example uses the function from the previous example, and adds an example of a lambda expression that uses the C++ Standard Library algorithm generate_n. このラムダ式は vector オブジェクトの要素を前の 2 つの要素の合計に代入します。This lambda expression assigns an element of a vector object to the sum of the previous two elements. 変更可能なキーワードを使用して、ラムダ式の本体が値によってキャプチャする外部変数 x および yのコピーを変更できるようにします。The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y, which the lambda expression captures by value. ラムダ式は元の変数 x および y を値でキャプチャするため、それらの値はラムダの実行後も 1 のまま残ります。Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.

// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename C> void print(const string& s, const C& c) {
    cout << s;

    for (const auto& e : c) {
        cout << e << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this is not thread-safe and is shown for illustration only
}

int main()
{
    // The number of elements in the vector.
    const int elementCount = 9;

    // Create a vector object with each element set to 1.
    vector<int> v(elementCount, 1);

    // These variables hold the previous two elements of the vector.
    int x = 1;
    int y = 1;

    // Sets each element in the vector to the sum of the
    // previous two elements.
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // lambda is the 3rd parameter
        // Generate current value.
        int n = x + y;
        // Update previous two values.
        x = y;
        y = n;
        return n;
    });
    print("vector v after call to generate_n() with lambda: ", v);

    // Print the local variables x and y.
    // The values of x and y hold their initial values because
    // they are captured by value.
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(v);
    print("vector v after 1st call to fillVector(): ", v);
    // Fill the vector with the next sequence of numbers
    fillVector(v);
    print("vector v after 2nd call to fillVector(): ", v);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

詳細については、「 generate_n」を参照してください。For more information, see generate_n.

constexpr ラムダ式constexpr lambda expressions

Visual Studio 2017 バージョン15.3 以降( /std: c++ 17で利用可能): ラムダ式は、キャプチャまたは導入される各データメンバーの初期化が定数式内で許可されている場合に constexpr として宣言するか、定数式で使用できます。Visual Studio 2017 version 15.3 and later (available with /std:c++17): A lambda expression may be declared as constexpr or used in a constant expression when the initialization of each data member that it captures or introduces is allowed within a constant expression.

    int y = 32;
    auto answer = [y]() constexpr
    {
        int x = 10;
        return y + x;
    };

    constexpr int Increment(int n)
    {
        return [n] { return n + 1; }();
    }

ラムダは、結果が constexpr 関数の要件を満たす場合に、暗黙的に constexpr ます。A lambda is implicitly constexpr if its result satisfies the requirements of a constexpr function:

    auto answer = [](int n)
    {
        return 32 + n;
    };

    constexpr int response = answer(10);

ラムダが暗黙的または明示的に constexprされる場合、関数ポインターへの変換では constexpr 関数が生成されます。If a lambda is implicitly or explicitly constexpr, conversion to a function pointer produces a constexpr function:

    auto Increment = [](int n)
    {
        return n + 1;
    };

    constexpr int(*inc)(int) = Increment;

Microsoft 固有の仕様Microsoft-specific

ラムダは、次の共通言語ランタイム (CLR) マネージエンティティではサポートされていません: ref クラスref 構造体値クラス、または値構造体Lambdas are not supported in the following common language runtime (CLR) managed entities: ref class, ref struct, value class, or value struct.

__Declspecなどの Microsoft 固有の修飾子を使用している場合は、parameter-declaration-clauseの直後のラムダ式に挿入できます。たとえば、次のようになります。If you are using a Microsoft-specific modifier such as __declspec, you can insert it into a lambda expression immediately after the parameter-declaration-clause—for example:

auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };

修飾子がラムダによってサポートされているかどうかを確認するには、ドキュメントの「 Microsoft 固有の修飾子」で、その修飾子に関する記事を参照してください。To determine whether a modifier is supported by lambdas, see the article about it in the Microsoft-Specific Modifiers section of the documentation.

C++ 11 の標準ラムダ機能に加えて、Visual Studio では、任意の呼び出し規約を使用する関数ポインターに変換できるステートレスなラムダがサポートされています。In addition to C++11 Standard lambda functionality, Visual Studio supports stateless lambdas, which are omni-convertible to function pointers that use arbitrary calling conventions.

参照See also

C++ 言語リファレンスC++ Language Reference
C++ 標準ライブラリの関数オブジェクトFunction Objects in the C++ Standard Library
関数呼び出しFunction Call
for_eachfor_each