Visual C++ 2012:stateless-lambdaとSQLiteのぷち拡張

CodeZine / 2013年2月21日 14時0分

 Visual C++ 2012は言語仕様とライブラリの両面でC++11に迫っています(完全準拠とまではいかないけども)。 いくつかの言語レベルでの変更/拡張の中から、lambdaに関するちょっとした機能とその応用を紹介します。

■はじめに

 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を含む)何らかの値を返すモノです。

list-01
// 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で与えています。

 比較する値を好きに指定したいなら:

list-02
// 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であることを示す[]の中に'='を添えることで「値キャプチャ」であることを示しています。キャプチャした変数を書き換えたいなら「参照キャプチャ:[&]」を使います:

list-03
// v要素の総和をsumに求める vector<int> v; int sum = 0; ... for_each(begin(v), end(v), [&](int item) { sum += item;});
 このlambdaのからくりはさほどにややこしいものではなく、コンパイラはlambda式をクラスに変換していると思ってくださいな。たとえば値キャプチャ:

list-04
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; }); }
であれば、

list-05
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); }
 こんな感じ。参照キャプチャも同様に:

list-06
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; }
を、

list-07
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; }
 なんてな変換を(裏でコッソリ)やってます。

 さて、一切キャプチャしない場合、このからくりに従うなら:

list-08
int main() { array<int,5> data = { 0, 1, 2, 3, 4 }; for_each(begin(data), end(data), [](int item) { cout << item << endl; }); }
は、

list-09
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)のだからわざわざクラスを起こさずとも:

list-10
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ではサポートされていませんでした。



CodeZine

この記事に関連するニュース

トピックスRSS

ランキング