概要
ラムダ式(無名関数(anonymous function, nameless function)、匿名関数)とは、関数を定義するための記法、文法のことである。
C++では、ラムダ式はC++11からサポートされた。g++コンパイラを使用する場合は、以下のようにC++11を有効にする必要がある。
また、C++11とC++14で仕様が異なる。C++14版は、より高度になっている。
g++49 -std=c++11 lambda.cpp c++ -std=c++11 lambda.cpp
C++のラムダ式以下の特徴がある。
- 関数オブジェクトであり、生成されたクラスのoperator()になる。
- std::functionに代入できる。
- STLの引数に使用できる。また、コールバック関数等の引数としてラムダ式を直接記述できる。
- ローカル変数をキャプチャできる。
- 引数や戻り値の型推論(auto)も利用できる。
ラムダ式の文法
int main(int argc, char *argv[])
{
[] // ラムダキャプチャー
() // パラメータ定義節
{} // 複合ステートメント
() // 関数呼び出し式
;
return 0;
}
最小のラムダ式の定義
int main(int argc, char *argv[])
{
auto func = [](){};
return 0;
}
引数は省略可能であるため、さらに省略できる。
int main(int argc, char *argv[])
{
auto func = []{};
return 0;
}
最小のラムダ式の呼び出し
ラムダ式を呼び出すには、関数呼び出し式()を使用する。
int main(int argc, char *argv[])
{
[](){}();
return 0;
}
サンプルコード
ラムダ式で書くHello World
#include <iostream>
int main(int argc, char const* argv[])
{
[]{ std::cout << "Hello World" << std::endl;}();
return 0;
}
ラムダ式を変数に代入する
ラムダ式を変数に代入することができる。関数ポインタのような感覚で呼び出せる。
#include <iostream>
int main(int argc, char const* argv[])
{
auto func = []{std::cout << "Hello world" << std::endl;};
func(); //ラムダ式の呼び出し
return 0;
}
ラムダ式を関数に渡す
ラムダ式を別の関数に渡すことができる。(コールバック関数)
#include <iostream>
template<typename Func>
void f(Func func)
{
func();
}
int main(int argc, const char *argv[])
{
f([]{std::cout << "Hello world" << std::endl;});
return 0;
}
ラムダ式に引数を与える
#include <iostream>
#include <string>
int main(int argc, const char *argv[])
{
[](const string &str) // 引数の定義
{ // 関数本体
std::cout << str << std:endl;
}
("I am Argument!"); // 関数呼び出しと引数
return 0;
}
ラムダ式で戻り値を返す
ラムダ式は、戻り値を返すことができる。
1つ目は、明示的に戻り値の型を定義していない。
2つ目は、明示的に戻り値の型を定義している。アロー演算子の記法([]()->型)で表現している。
#include <iostream>
int main(int argc, const char *argv[])
{
auto a = []{return 0;}; // 戻り値を推測させる
auto b = []() -> float {return 3.14;}; // 戻り値を明示的に定義する
std::cout << a << std::endl;
std::cout << b << std::endl;
return 0;
}
変数のキャプチャ
ラムダ式は、式が定義される関数のスコープの変数をキャプチャできる。
JavaScriptでいうクロージャが、スコープにある変数を参照できることをイメージすること。
#include <iostream>
#include <string>
int main(int argc, char const* argv[])
{
string x = "I am string";
[&]{std::cout << x << std::endl;}(); // 参照
[=]{std::cout << x << std::endl;}(); // コピー
return 0;
}
参照の場合の代入
参照での代入とコピーでの代入を行う。
#include <iostream>
#include <string>
int main(int argc, const char *argv[])
{
string s1 = "I am s1";
[&]{s1 = "Overwrite s1!";}();
//string s2 = "I am s2";
//[=]{s1 = "Overwrite s2!";}(); C++11ではコンパイルエラーが起きる
std::cout << s1 << std::endl;
//std::cout << s2 << std::endl;
return 0;
}
mutable
コピーでの代入では、変数を書き変えることができないが、mutableを使用することで、ラムダ式の中だけ変数を書き変えることができる。
しかし、コピーのため、元の変数の値は変更できない。
mutableを指定した時、ラムダ式の引数リストの括弧は省略できない。
#include <iostream>
#include <string>
int main(int argc, const char *argv[])
{
std::string s1 = "I am s1";
[=]() mutable {s1 = "Overwrite s1!"; std::cout << s1 << std::endl;}();
std::cout << s1 << std::endl;
return 0;
}
また、mutableを指定した時、明示的に戻り値を指定する場合は、以下のようにする。
auto dRet = []() mutable -> double {return 3.14;};
キャプチャの指示
ラムダキャプチャ[]には、変数ごとにキャプチャの方法(コピーまたは参照)を指定できる。
#include <iostream>
int main(int argc, const char *argv[])
{
int a = 0,
b = 0;
[a, &b]() mutable {a = 1; b = 1;}();
std::cout << a << std::endl; // 0
std::cout << b << std::endl; // 1
return 0;
}
また、一部の変数だけ指定して、残りは、キャプチャのデフォルトを指定することができる。
#include <iostream>
int main(int argc, const char *argv[])
{
int a = 0,
b = 0,
c = 0,
d = 0;
// a, bを参照、それ以外はコピー
[=, &a, &b]() mutable {}();
// a, bはコピー、それ以外は参照
[&, a, b]() mutable {}();
return 0;
}
また、以下のように、デフォルトと同じキャプチャは指定できない。
int a = 0,
b = 0;
[&, &a]{}(); // エラー
[=, a]{}(); // エラー
[a, a]{}(); // エラー
thisポインタとキャプチャ
thisポインタは、ポインタであるため、キャプチャがコピーでも参照でも、以下のように変数iは変更される。
#include <iostream>
typedef struct tag_S
{
int i;
void f()
{
[=]{this->i = 1;}();
}
}S;
int main(int argc, char const* argv[])
{
S s;
s.f();
return 0;
}
ラムダ式を返す
std::functionを使用することで、ラムダ式を関数から返すことができる。
キャプチャがコピーであるため、変数strは破棄されない。
#include <iostream>
#include <string>
#include <functional>
std::function<void()> f()
{
std::string str = "Hoge";
return [=]{std::cout << str << std::endl;};
}
int main(int argc, const char *argv[])
{
auto func = f();
func(); // 代入して呼び出し
f()(); // 代入せずに呼び出し
return 0;
}