C++のラムダ式はファンクタだったのか…

2020-03-12

今までまったく別物だと思ってたんだけど、C++のラムダ式は実のところファンクタの糖衣構文でしかないということに気づいた。

例えば以下のようなラムダ式:

auto f = [](int x) {
return x * x;
};

f(1111); // => 1234321

は、内部的には operator() を実装するクラスが作られて使用されるのと意味的には同じ:

struct F {
int operator()(int x) {
return x * x;
}
};

F f;

f(1111); // => 1234321

で、その場でしか使用しない処理をわざわざクラスを作成せずに簡潔に書けるというのが利点ということになる。

変数がキャプチャされるケースも、コンストラクタでメンバ変数として保持してやれば実現できる。

注意点としては、 [=] によりコピーキャプチャされた変数の書き換えはコンパイルエラーが出る:

int y = 0;
auto f = [y](int x) -> int {
y += x; // <= error: cannot assign to a variable captured by copy in a non-mutable lambda
return y;
};

ラムダ式により生成されるファンクタはデフォルトでは const となること原因:

int y = 0;

class F {
int y;
public:
F(int y_) : y(y_) {}
int operator()(int x) { // <= メンバ変数(キャプチャ内容)を書き換えるので、const指定できない
y += x;
return y;
}
};

const F f(y); // <= デフォルトで const 指定

f(1111); // <= operator()を呼び出せず、エラー

とのこと。 デフォルトでは書き換えられないが、エラーメッセージの通り mutable を指定すれば書き換えられる:

int y = 0;
auto f = [y](int x) mutable -> int { // <= mutable を指定
y += x; // <= 書き換え可能
return y;
};
cout << f(1) << endl; // => 1
cout << f(2) << endl; // => 3
cout << f(3) << endl; // => 6

cout << y << endl; // => 0 (コピーキャプチャなので、キャプチャ元の変数は変わらず)

これはたぶん、書き換えられると意味的にまぎらわしいからデフォルトでは const になっているのだと推測している。 というのは、ラムダ式をコピーするとキャプチャ自体もコピーされるので、実体が枝分かれしてしまう:

int y = 0;
auto f = [y](int x) mutable -> int {
y += x;
return y;
};
auto g = f;
f(1); // <= 1
g(10); // <= 10 (fの呼び出しによる1増加はこちらには反映しない)

参考