ラムダ関数
概要
C++03では、STLのアルゴリズムを使用する際に、指定する関数オブジェクトを事前に定義しておく必要があった。 一時的に使用するものであっても定義を必要とするので不便であったため、C++11では関数オブジェクトを定義とともに構築できるラムダ関数が追加された。 つまり、ラムダ関数は関数オブジェクトと同じように振る舞う。 ラムダ関数は定義を記述したスコープ内の変数の値を参照することが可能で、これをキャプチャと呼ぶ。キャプチャにはコピーキャプチャと参照キャプチャの2種類がある。 また、特定の変数のみコピーキャプチャで、その他は参照キャプチャとするような制御も可能になっている。
ラムダ関数の定義は少し特殊な構文となっており、以下にその構文を示す。
[キャプチャリスト] 引数 Trailing-Return-Type { 関数本体 }
キャプチャリストは、カンマ区切りのキャプチャである。
[キャプチャ, キャプチャ, ...]
上記の中で引数、Trailing-Return-Typeはそれぞれ省略することが可能である。この場合の構文は以下のようになる。
[キャプチャリスト] { 関数本体 }
この時、引数を()で記述したものと同じとなり、また戻り値の型は以下の条件で決定される。
- 関数本体が一つの return 文で構成されている場合、returnに指定した式の型
- 上記以外の場合は void
class X {
public:
void f(std::vector<int> v) {
std::sort(v.begin(), v.end(),
[] (int a, int b) -> int {
return a < b;
}
);
}
};
続いて、[キャプチャリスト]
について説明する。これをラムダキャプチャと呼び、ラムダ関数を定義した関数内のローカル変数を参照する機能である。
参照したいローカル変数をキャプチャリストに記述することで、ラムダ関数内で利用することが可能となる。変数のキャプチャにはコピーキャプチャ、参照キャプチャの2種類がある。コピーキャプチャは読み込みのみ可能で、参照キャプチャは読み書き可能。
多くの変数をキャプチャする場合にキャプチャリストに列挙するのは面倒であるため、デフォルトのキャプチャ方式を指定することも可能である。 但し、デフォルトキャプチャはバグの温床となるため、明示的に変数を指定するべきである。
以下にキャプチャリストの指定方式を記載する。
デフォルトキャプチャ | 記述 |
---|---|
コピーキャプチャ | [=] |
参照キャプチャ | [&] |
変数キャプチャ | 記述 |
---|---|
コピーキャプチャ | [変数名,...] |
参照キャプチャ | [&変数名,...] |
thisキャプチャ | [this,...] |
デフォルトキャプチャと変数キャプチャは組み合わせて指定可能であり、デフォルトはコピー、一部の変数は参照というような使い方もできるようになっている。但し、デフォルトがコピーキャプチャの場合、後に続く変数キャプチャにはコピーキャプチャは指定できず、その逆も同様である。 thisキャプチャはメンバ関数内で使用でき、thisキャプチャを行うことで非staticメンバへのアクセスはthisを用いなくてもアクセス可能となる。 なお、デフォルトキャプチャによりキャプチャされる対象にthisも含まれる。
変数キャプチャのみを用いる場合は上記のような制約は無い。
void func() {
int a, b, c d;
[&]() {
a=b; //OK
c=d; //OK
};
[=]() {
int x=a; //OK
int b=a; //error
};
[&, a]() { //aのみコピーキャプチャ
b=a; //OK
a=1; //error
};
[a, &b, c, &d]() {
b=a; //OK
c=a; //error
};
}
ラムダ関数の定義は、最終的にテンポラリオブジェクトとして評価されるため、変数への格納や、そのまま実行することも可能である。 変数へ格納する際、lambda関数の型は未定義のため、autoによる型推論を行うか、関数オブジェクトを代入可能なstd::function等を用いる。
void func() {
auto f = []() {
std::cout << "hello world!" << std::endl;
};
[]() {
std::cout << "hello world!" << std::endl;
}();
}