C++の基礎 - ラムダ式

提供:MochiuWiki : SUSE, EC, PCB
2021年11月18日 (木) 10:25時点におけるWiki (トーク | 投稿記録)による版 (文字列「</source>」を「</syntaxhighlight>」に置換)
ナビゲーションに移動 検索に移動

概要

ラムダ式(無名関数(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>