C++の基礎 - ラムダ式
概要
ラムダ式(無名関数(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)も利用できる。
ラムダ式の文法
<source lang="c++"> int main(int argc, char *argv[]) { [] // ラムダキャプチャー () // パラメータ定義節 {} // 複合ステートメント () // 関数呼び出し式 ; return 0; } </syntaxhighlight>
最小のラムダ式の定義
<source lang="c++"> int main(int argc, char *argv[]) { auto func = [](){}; return 0; } </syntaxhighlight>
引数は省略可能であるため、さらに省略できる。
<source lang="c++"> int main(int argc, char *argv[]) { auto func = []{}; return 0; } </syntaxhighlight>
最小のラムダ式の呼び出し
ラムダ式を呼び出すには、関数呼び出し式()を使用する。
<source lang="c++"> int main(int argc, char *argv[]) { [](){}(); return 0; } </syntaxhighlight>
サンプルコード
ラムダ式で書くHello World
<source lang="c++"> #include <iostream> int main(int argc, char const* argv[]) { []{ std::cout << "Hello World" << std::endl;}(); return 0; } </syntaxhighlight>
ラムダ式を変数に代入する
ラムダ式を変数に代入することができる。関数ポインタのような感覚で呼び出せる。
<source lang="c++"> #include <iostream> int main(int argc, char const* argv[]) { auto func = []{std::cout << "Hello world" << std::endl;}; func(); //ラムダ式の呼び出し return 0; } </syntaxhighlight>
ラムダ式を関数に渡す
ラムダ式を別の関数に渡すことができる。(コールバック関数)
<source lang="c++"> #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; } </syntaxhighlight>
ラムダ式に引数を与える
<source lang="c++"> #include <iostream> #include <string> int main(int argc, const char *argv[]) { [](const string &str) // 引数の定義 { // 関数本体 std::cout << str << std:endl; } ("I am Argument!"); // 関数呼び出しと引数 return 0; } </syntaxhighlight>
ラムダ式で戻り値を返す
ラムダ式は、戻り値を返すことができる。
1つ目は、明示的に戻り値の型を定義していない。
2つ目は、明示的に戻り値の型を定義している。アロー演算子の記法([]()->型)で表現している。
<source lang="c++"> #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; } </syntaxhighlight>
変数のキャプチャ
ラムダ式は、式が定義される関数のスコープの変数をキャプチャできる。
JavaScriptでいうクロージャが、スコープにある変数を参照できることをイメージすること。
<source lang="c++"> #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; } </syntaxhighlight>
参照の場合の代入
参照での代入とコピーでの代入を行う。
<source lang="c++"> #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; } </syntaxhighlight>
mutable
コピーでの代入では、変数を書き変えることができないが、mutableを使用することで、ラムダ式の中だけ変数を書き変えることができる。
しかし、コピーのため、元の変数の値は変更できない。
mutableを指定した時、ラムダ式の引数リストの括弧は省略できない。
<source lang="c++"> #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; } </syntaxhighlight>
また、mutableを指定した時、明示的に戻り値を指定する場合は、以下のようにする。
<source lang="c++"> auto dRet = []() mutable -> double {return 3.14;}; </syntaxhighlight>
キャプチャの指示
ラムダキャプチャ[]には、変数ごとにキャプチャの方法(コピーまたは参照)を指定できる。
<source lang="c++"> #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; } </syntaxhighlight>
また、一部の変数だけ指定して、残りは、キャプチャのデフォルトを指定することができる。
<source lang="c++"> #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; } </syntaxhighlight>
また、以下のように、デフォルトと同じキャプチャは指定できない。
<source lang="c++"> int a = 0, b = 0; [&, &a]{}(); // エラー [=, a]{}(); // エラー [a, a]{}(); // エラー </syntaxhighlight>
thisポインタとキャプチャ
thisポインタは、ポインタであるため、キャプチャがコピーでも参照でも、以下のように変数iは変更される。
<source lang="c++"> #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; } </syntaxhighlight>
ラムダ式を返す
std::functionを使用することで、ラムダ式を関数から返すことができる。
キャプチャがコピーであるため、変数strは破棄されない。
<source lang="c++"> #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; } </syntaxhighlight>