そろそろC++11も普及しているようだし、ラムダ式を使ってみる

「ラムダ式(lambda expressions)」は、簡易的な関数オブジェクトをその場で定義するための機能である。

この機能によって、「高階関数(関数を引数もしくは戻り値とする関数)」をより使いやすくできる。

javascriptの

((function(){
})());

みたいなことができるようになったということなので試してみる。

参考

作って即利用する

#include <iostream>

int main(int argc, const char * argv[]) {

  [](){ std :: cout << "Lamda" << std :: endl; }();

  return 0;
}

出力結果

Lamda
Program ended with exit code: 0

ラムダ式の文法

int main(int argc, char *argv[]) {
	[]	// ラムダキャプチャー
	()	// パラメータ定義節
	{}	// 複合ステートメント
	()	// 関数呼び出し式
	;
	return 0;
}

というように、[](){} の3つの括弧で関数を宣言する。 それを実行するために、お尻に()がくっついている。

返り値を明示する

通常で返り値は自動的に判断してくれる。

#include <iostream>

int main(int argc, const char * argv[]) {

  std :: cout << [](){ return 3299342; }() << std :: endl;
  
  return 0;
}

出力結果

3299342
Program ended with exit code: 0

返り値を明示することができる。

#include <iostream>

int main(int argc, const char * argv[]) {

  std :: cout << []() -> float { return 3299342; }() << std :: endl;
  
  return 0;
}

出力結果

3.29934e+06
Program ended with exit code: 0

戻り値の型の指定

戻り値の型を指定するには以下のようにします。

auto f1_double = [](int x) -> double { return x * x; };
std::cout << "10000: " << f1_double(10000) << std::endl;  // "10000: 1e+08"と出力される

変数に入れる

javascriptのfunction同様、変数に代入することができる。

#include <iostream>

int main(int argc, const char * argv[]) {

  auto fx = [](int i){ std :: cout << i << std :: endl; };

  fx(100);

  return 0;
}

出力結果

100
Program ended with exit code: 0

明示的に型指定をした std :: function オブジェクトに代入することもできる。 std :: functionを利用する場合、 #include <functional> が必要。

#include <iostream>
#include <functional>

int main(int argc, const char * argv[]) {

  std :: function<void(int)> fx = [](int i){ std :: cout << i << std :: endl; };

  fx(100);

  return 0;
}

出力結果

100
Program ended with exit code: 0

std::function

  • ラムダ式を変数に保存できるのが、std::function 。関数ポインタの次世代版
  • ただ、基本は auto で受け取ること(std::function によって型消去されないため、autoの方が早い)

使い方

std::function< 戻り値の型(引数の型) > f = ラムダ式;

std::function< int(void) > f = []{ return 0; };
f(); // 0が返る

一応、関数ポインタに代入することも可能


#include <iostream>

int main(int argc, const char * argv[]) {

    void (*fx)(int) = [](int i){ std :: cout << i << std :: endl; };

  fx(100);

  return 0;
}

出力結果

100
Program ended with exit code: 0

ラムダ式を関数ポインタ型へ変換する

これは割と一般的な知識だと思うんですが、『キャプチャしていない』ラムダ式は関数ポインタ型へとキャストする事が出来ます。

using func_t = int(*)(int);
// 関数ポインタ型として受け取る事が出来る
func_t twice = [](int n){ return n + n; };

ただし、これらはキャプチャーという機能を有効にすると利用できないらしい。

キャプチャ(外部の変数)を利用する

外部の変数を利用(キャプチャ)できるようにすることができる。

#include <iostream>
#include <string>

int main(int argc, const char * argv[]) {

  std :: string str("string");
  
  str = [=](){ return str + " lamda string"; }();

  std :: cout << str << std :: endl;


  return 0;
}

実行結果

string lamda string
Program ended with exit code: 0

これができると返り値があるブロック { ... } のようなものができて、一連の処理とその場限りで利用する変数をまとめるのに有効な気がする。

外部の変数を参照型でキャプチャすることもできる。

#include <iostream>
#include <string>

int main(int argc, const char * argv[]) {

  std :: string str("string");
  
  [&](){ str = "lamda string"; return str; }();

  std :: cout << str << std :: endl;


  return 0;
}

実行結果

lamda string
Program ended with exit code: 0

他にもさまざまな指定方法があるが、参照渡しの場合変数の寿命とか、値渡しの場合は変数のサイズとか、きにしなければならない問題がある。

キャプチャ

ラムダ式には、ラムダ式の外にある自動変数を、ラムダ式内で参照できるようにする「キャプチャ(capture)」という機能がある。 キャプチャは、ラムダ導入子(lambda-introducer)と呼ばれる、ラムダ式の先頭にある [ ] ブロックのなかで指定する。

キャプチャには、コピーキャプチャと参照キャプチャがあり、デフォルトでどの方式でキャプチャし、個別の変数をどの方式でキャプチャするかを指定できる。

キャプチャ記法 説明
[&] デフォルトで環境にある変数を参照して、ラムダ式のなかで使用する
[=] デフォルトで環境にある変数をコピーして、ラムダ式のなかで使用する
[&x] 変数xを参照して、ラムダ式のなかで使用する
[x] 変数xをコピーして、ラムダ式のなかで使用する
[&, x] デフォルトで参照キャプチャ、変数 x のみコピーして、ラムダ式のなかで使用する
[=, &x] デフォルトでコピーキャプチャ、変数 x のみ参照して、ラムダ式のなかで使用する
[this] *this のメンバを参照して、ラムダ式のなかで使用する
[this, x] *this のメンバを参照し、変数 x のみコピーして、ラムダ式のなかで使用する

mutable

キャプチャした変数はクロージャオブジェクトのメンバ変数と見なされ、クロージャオブジェクトの関数呼び出し演算子は、 デフォルトで const 修飾される。そのため、コピーキャプチャした変数をラムダ式のなかで書き換えることはできない。

int rate = 2;
[rate](int x) -> int { return x * ++rate; } // エラー!rate変数を書き換えることはできない

コピーキャプチャした変数を書き換えたい場合は、ラムダ式のパラメータリストの後ろに mutable と記述する。

int rate = 2;
[rate](int x) mutable -> int { return x * ++rate; } // OK

いろいろ調べてみたが今の所 const & 型でキャプチャする方法はなさそう。

返り値や引数をラムダ式にする

もちろん、返り値や引数にラムダ式を渡すことができる。

#include <iostream>
#include <functional>

std :: function<const int(const int)> Fx( const std :: function<const int(const int)> &_fx );


int main(int argc, const char * argv[]) {

  int var = Fx([](const int x){ 
      return x; 
  })(200);

  std :: cout << "return function : " << var << std :: endl;
  
  return 0;
}

std :: function<const int(const int)> Fx( const std :: function<const int(const int)> &_fx ){

  int x = _fx(100);

  std :: cout << "argument function : " << x << std :: endl;

  return [=](const int y) -> const int { return x + y; };
}

実行結果

argument function : 100
return function : 300
Program ended with exit code: 0

キャプチャ機能を利用しない場合に限り、関数ポインタで渡すこともできる。


#include <iostream>
#include <functional>

const int (*Fx (const int (*_fx) (const int) ) ) (const int);

int main(int argc, const char * argv[]) {

  int var = Fx([](const int x) -> const int { return x; })(200);

  std :: cout << "return function : " << var << std :: endl;
  
  return 0;
}

const int (*Fx(const int (*_fx) (const int) ) ) (const int){



  std :: cout << "argument function : " << _fx(100) << std :: endl;

  return [](const int y) -> const int { return y; };

}

実行結果

argument function : 100
return function : 200
Program ended with exit code: 0

キャプチャ機能を利用してラムダ式を返り値にしようとすると、こんなエラーが出る

No viable conversion from returned value of type '(lambda at main.cpp:48:10)' to function return type 'const int (*)(const int)'