CodeZine(コードジン)

特集ページ一覧

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

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2013/02/21 14:00

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


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • επιστημη(エピステーメー)

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5