はじめに
Windows 8ストアアプリが書ける開発環境Visual studio 2012(VS2012)のリリースからおよそ半年、僕の愛機にはVisual Studio 2010と2012が仲良く同居しています。メンテナンスの必要なプロジェクトはともかく、新規プロジェクトはすべてVS2012で起こすようになりました。
Visual C++ 2012(vc11)はgccやclangと比べて「C++11対応が手ぬるい!」とC++の猛者には評判いまひとつの感がありますが、それでもvc10よりはずっと良くなってますし、Visual Studio本体とは別にVisual C++独自のupdateも行うとアナウンスされているので、しばらくは様子を見ようと考えています。
vc11で追加された機能のひとつ:「stateless-lambdaの関数ポインタへの暗黙変換」は、地味ながらも面白いことができそうで、すこしばかり遊んでみることにしました。
lambdaのからくり
lambdaは関数オブジェクト、つまりoperator()によって(voidを含む)何らかの値を返すモノです。
// vの中から 0 を見つける vector<int> v; ... auto i = find_if(begin(v), end(v), [](int item) { return item == 0;}); if ( i != end(v) ) { /* 見つかった! */ }
find_ifに、「要素が0ならtrueを返す関数オブジェクト」をlambdaで与えています。
比較する値を好きに指定したいなら:
// vの中から target を見つける vector<int> v; int target; ... auto i = find_if(begin(v), end(v), [=](int item) { return item == target;}); if ( i != end(v) ) { /* 見つかった! */ }
この例ではlambda式の中にローカル変数targetを引き込んでいます。lambda式に変数を引き込むことをキャプチャ(capture)といいます。lambda内で引き込んだtargetを書き換えることはないので「値キャプチャ」です。lambdaであることを示す[]の中に'='を添えることで「値キャプチャ」であることを示しています。キャプチャした変数を書き換えたいなら「参照キャプチャ:[&]」を使います:
// v要素の総和をsumに求める vector<int> v; int sum = 0; ... for_each(begin(v), end(v), [&](int item) { sum += item;});
このlambdaのからくりはさほどにややこしいものではなく、コンパイラはlambda式をクラスに変換していると思ってくださいな。たとえば値キャプチャ:
int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; int n = 5; for_each( begin(data), end(data), [=](int item) { cout << n + item << endl; }); }
であれば、
namespace { class lambda { int val_capture; public: lambda(int n) : val_capture(n) {} void operator()(int item) { cout << val_capture + item << endl; } }; } int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; int n = 5; lambda fn_obj(n); // ここでキャプチャ for_each( begin(data), end(data), fn_obj); }
こんな感じ。参照キャプチャも同様に:
int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; int n = 0; for_each( begin(data), end(data), [&](int item) { n += item; }); cout << n << endl; }
を、
namespace { class lambda { int& ref_capture; public: lambda(int& n) : ref_capture(n) {} void operator()(int item) { ref_capture += item; } }; } int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; int n = 0; lambda fn_obj(n); // ここでキャプチャ for_each( begin(data), end(data), fn_obj); cout << n << endl; }
なんてな変換を(裏でコッソリ)やってます。
さて、一切キャプチャしない場合、このからくりに従うなら:
int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; for_each(begin(data), end(data), [](int item) { cout << item << endl; }); }
は、
namepsace { class lambda { public: void operator()(int item) { cout << item << endl; } }; } int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; lambda fn_obj; for_each( begin(data), end(data), fn_obj); }
となります。が、クラスがキャプチャに必要なメンバ変数を一切持たない(保持すべき状態がない=stateless)のだからわざわざクラスを起こさずとも:
namespace { void fn_obj(int item) { cout << item << endl; } } int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; for_each( begin(data), end(data), &fn_obj); }
これで十分ですよね。キャプチャしないlambdaは(非メンバ)関数と等価なんだから、関数ポインタに暗黙変換されてもいいじゃない。というのが「stateless-lambdaの関数ポインタへの暗黙変換」です。この機能、C++11言語仕様には書かれているんですけど、vc10ではサポートされていませんでした。