nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

ラムダ式攻略 [C++]

はじめに

「ラムダ式(lambda expressions)」は、簡易的な関数オブジェクトをその場で定義するための機能です。

この機能によって、「高階関数(関数を引数もしくは戻り値とする関数)」をより使いやすくできます。

仕様

構文

[キャプチャリスト](パラメータリスト) mutable 例外仕様 属性 -> 戻り値の型 { 関数の本体 }

`

  • パラメータ、mutable、例外仕様、属性、戻り値の型のいずれも明示的に指定しない場合は、パラメータリストの丸カッコを省略できる
  • コピーキャプチャした変数を書き換える必要がない場合、mutableを省略できる
  • 例外仕様を指定しない場合、それを省略できる
  • 属性を指定しない場合、それを省略できる
  • 戻り値の型を推論に任せる場合、->記号および戻り値の型を省略できる

もっとも短いラムダ式は、以下のようになります。 []{}

キャプチャ

ラムダ式には、ラムダ式の外にある自動変数を、ラムダ式内で参照できるようにする「キャプチャ(capture)」という機能があります。

キャプチャは、ラムダ導入子(lambda-introducer)と呼ばれる、ラムダ式の先頭にある[ ]ブロックのなかで指定します。

キャプチャには、コピーキャプチャと参照キャプチャがあり、デフォルトでどの方式でキャプチャし、個別の変数をどの方式でキャプチャするかを指定できます。

キャプチャ記法 説明
[&] デフォルトで環境にある変数を参照して、ラムダ式のなかで使用する
[=] デフォルトで環境にある変数をコピーして、ラムダ式のなかで使用する
[&x] 変数xを参照して、ラムダ式のなかで使用する
[x] 変数xをコピーして、ラムダ式のなかで使用する
[&, x] デフォルトで参照キャプチャ、変数xのみコピーして、ラムダ式のなかで使用する
[=, &x] デフォルトでコピーキャプチャ、変数xのみ参照して、ラムダ式のなかで使用する
[this] *thisのメンバを参照して、ラムダ式のなかで使用する
[this, x] *thisのメンバを参照し、変数xのみコピーして、ラムダ式のなかで使用する

実際にラムダ式を使用してみる

ラムダ式を変数に格納する。引数を与える。

ラムダ式を変数に格納します。また、ラムダ式に引数を与えます。

#include <iostream>
int main(void){

    auto func1 = [](){ std::cout << "Paramter No Set." << std::endl; };
    
    func1();
    
    auto func2 = [](int x){ std::cout << "Paramter Set : " << x << std::endl; };
    
    func2(5);
    
    return 0;
}
Paramter No Set.
Paramter Set : 5

関数の引数内にラムダ式を適用する

ラムダ式は関数の引数にも適用可能です。

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

using namespace std;

int main(void)
{
    std::vector<int> v1 = {1,2,3,4};
    
    // 関数の引数内に関数作成
    std::for_each(v1.begin(), v1.end(), [](int n){ std::cout << "Paramter : " << n << std::endl; });
}
Paramter : 1
Paramter : 2
Paramter : 3
Paramter : 4

キャプチャを使用する

キャプチャを使用することで、ラムダ式外にある変数を使用可能です。 以下は、コピーキャプチャを行う場合です。

ラムダ式の中で、キャプチャした変数を書き換えることができません。書き換える場合は、mutable指定子が必要です。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [a, b](){ return a + b; };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}
a : 1
b : 2
a + b : 3

ラムダ式の中で値を書き換える場合は、 [&a, &b]のように記載します。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [&a, &b]()
    {
        a = 5;
        b = 10;
        return a + b;
    };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}

変数を羅列するのが面倒なら=と書きます。すべての外側の変数がリードオンリーで見えるようになります。

auto f_offset = [=](int n){ return a * n + b; };

初期化キャプチャ [C++14]

C++14から、キャプチャで変数を初期化できるようになりました。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [a = 5, b = 10]()
    {
        return a + b;
    };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}
a : 1
b : 2
a + b : 15

ジェネリックラムダ (C++14)

ジェネリックラムダ(generic lambdas)は、C++11のラムダ式を拡張して、パラメータにテンプレートを使用できるようにした機能です。

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

このラムダ式は、以下のような関数呼び出し演算子を持つ関数オブジェクトを生成します。

template <class T1, class T2>
auto operator()(T1 a, T2 b) const
{
  return a + b;
}
#include <iostream>
#include <string>

using namespace std::string_literals;

int main(void){
    
  auto plus = [](auto a, auto b) { return a + b; };
  // ラムダ式のパラメータ型をautoにすることで、
  // 任意の型をパラメータとして受け取れる
  int result1 = plus(3, 2);
  std::string result2 = plus("Hello"s, "World"s);

  std::cout << result1 << std::endl;
  std::cout << result2 << std::endl;
}
5
HelloWorld

上記コードでは、sリテラルを使用しています。 文字列リテラルを受け取り、各文字型のbasic_stringオブジェクトを構築します。 std::string型に文字列を格納する際に、文字列を厳密に指定可能です。

参考リンク

C++ でのラムダ式 | Microsoft Docs

ラムダ式 - cpprefjp C++日本語リファレンス

クロージャデザインパターン