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