🖥️

【C++言語入門】 第23回 ラムダ式

に公開

https://youtu.be/5tjLQTzeW1Y

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回からは何回かに分けて ラムダ式 関連のお話しをしていきますわ

\footnotesize \textcolor{lime}{ずんだもん:} ラムダ式

\footnotesize \textcolor{pink}{四国めたん:} はい、関数やメソッド内で定義できる関数の形式ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 関数内で関数を定義できるのか?

\footnotesize \textcolor{pink}{四国めたん:} その辺りも含めてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} よろしくお願いなのだ

関数内に関数を定義できる?

\footnotesize \textcolor{pink}{四国めたん:} まず、 ラムダ式 についてお話しする前に、関数内に関数を定義することを考えてみますわ

\footnotesize \textcolor{lime}{ずんだもん:} そんなことができるのか?

\footnotesize \textcolor{pink}{四国めたん:} まずはメイン関数と、足し算をするためのAdd関数を作成してみましょう

#include <iostream>

int Add(int a, int b) {
  int c = a + b;
  return c;
}

int main(int argc, char* argv[]) {
  int d = Add(1, 2);
  std::cout << d << std::endl;
  return 0;
}

add関数

\footnotesize \textcolor{lime}{ずんだもん:} 問題なく動作するのだ

\footnotesize \textcolor{pink}{四国めたん:} 次にAdd関数をメイン関数内に移動してみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int Add(int a, int b) {
    int c = a + b;
    return c;
  }

  int d = Add(1, 2);
  std::cout << d << std::endl;
   return 0;
 }

エラー

\footnotesize \textcolor{lime}{ずんだもん:} エラーになったのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} ごく一部のコンパイラでは問題なくコンパイルができて、正常に動作するのですが...

\footnotesize \textcolor{lime}{ずんだもん:} Visual Studioではダメだったのだ

\footnotesize \textcolor{pink}{四国めたん:} C言語でもC++言語でも、一般的には関数内に関数を定義することはできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} 実際には関数内に関数を定義しなくても、外に定義すればOKなので、特に問題は無いと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかにその通りなのだ

関数内で関数を定義するための抜け道

C言語では難しい方法ですが、C++言語では関数内で関数を定義するための抜け道が存在します

それは、関数内で構造体やクラスを定義し、その中でpublic:指定の静的メソッドを定義する方法です

どちらかと云うと、デフォルトでpublic:な構造体を使う方が良いでしょう

例えばAdd関数を定義したい場合には以下のようにすれば可能です

#include <iostream>

int main(int argc, char* argv[]) {
  struct OP {
    static int Add(int a, int b) {
      int c = a + b;
      return c;
    }
  };

  int d = OP::Add(1, 2);
  std::cout << d << std::endl;
   return 0;
 }

メイン関数内でOPと云う構造体を定義し、その中でAdd関数を定義しています。

関数内にはラムダ式を使いましょう

\footnotesize \textcolor{lime}{ずんだもん:} それでも関数内で簡単な関数を定義したいと思うのだ

\footnotesize \textcolor{pink}{四国めたん:} まぁ、そういう要望は結構あるようですわね

\footnotesize \textcolor{pink}{四国めたん:} そこで、LISPなど、他の言語で導入が進んでいた ラムダ式 という方法がC++11から導入されましたわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、うれしいのだ

\footnotesize \textcolor{pink}{四国めたん:} ところで ラムダ式 は通常の関数とは少し異なる形式をしていますわ

\footnotesize \textcolor{lime}{ずんだもん:} どんな形式なのだ?

\footnotesize \textcolor{pink}{四国めたん:} このような形になりますわ

[キャプチャリスト](引数...) -> 戻り値の型 { 処理 }

\footnotesize \textcolor{lime}{ずんだもん:} どう見ても関数名に相当するものがないのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、 ラムダ式 は、いわゆる 無名関数 (nameless function)とか 匿名関数 (anonymous function) とかの一種ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 名前から推測すると、関数名がないのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、その通りですわ

\footnotesize \textcolor{lime}{ずんだもん:} では、どのように使うのだ?

\footnotesize \textcolor{pink}{四国めたん:} ラムダ式 を定義と同時に使用するのが基本ですわね

\footnotesize \textcolor{pink}{四国めたん:} その場合には、処理の後に実引数を指定しますわ

[キャプチャリスト](引数...) -> 戻り値の型 { 処理 } (実引数...);

\footnotesize \textcolor{lime}{ずんだもん:} 各部分について、詳しく教えて欲しいのだ

\footnotesize \textcolor{pink}{四国めたん:} わかりましたわ

キャプチャリスト

\footnotesize \textcolor{pink}{四国めたん:} まず、 キャプチャリスト は、関数としては、かなり特殊な役割を持っていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 特殊な役割?

\footnotesize \textcolor{pink}{四国めたん:} はい

\footnotesize \textcolor{pink}{四国めたん:} まず、 ラムダ式 は関数内で定義しますわ

\footnotesize \textcolor{lime}{ずんだもん:} そのための ラムダ式 なのだ

\footnotesize \textcolor{pink}{四国めたん:} そして、 キャプチャリスト には既に関数で宣言されている変数をカンマ区切りでリストアップしますわ

\footnotesize \textcolor{lime}{ずんだもん:} リストアップした変数はどうするのだ?

\footnotesize \textcolor{pink}{四国めたん:} リストアップした変数は、そのまま ラムダ式 の処理内で使用することができるようになりますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、それは便利なのだ

\footnotesize \textcolor{lime}{ずんだもん:} でも キャプチャリスト ではなく、引数に渡せば同じように関数で宣言された変数を使うことができるのではないか?

\footnotesize \textcolor{pink}{四国めたん:} その通りですわね

\footnotesize \textcolor{pink}{四国めたん:} ただ、 ラムダ式 の高度な使い方をするようになると、 キャプチャリスト が重要になってくるのですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか...

\footnotesize \textcolor{pink}{四国めたん:} なお、 ラムダ式 の後に宣言された変数は キャプチャリスト に含めることができませんので注意が必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} ラムダ式 内で必要になる変数は、先に宣言、初期化しておくのだ

コピーキャプチャ

\footnotesize \textcolor{pink}{四国めたん:} キャプチャリスト に変数名をそのまま記述すると、 ラムダ式 の処理内では変数のコピーを使うことになりますわ

\footnotesize \textcolor{lime}{ずんだもん:} そのあたりは通常の関数の引数と同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、これらの変数を コピーキャプチャ と云いますわ

\footnotesize \textcolor{lime}{ずんだもん:} そのまんまのネーミングだな

\footnotesize \textcolor{pink}{四国めたん:} ただ、通常の関数とは異なり、配列についてもコピーされるので、サイズの大きい配列の指定には注意が必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 配列もコピーされるなんて、配列は引数で渡した方が良いかも...

\footnotesize \textcolor{pink}{四国めたん:} 試しに配列を使った ラムダ式 を使ってみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [a, b]() {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
  }();
  return 0;
 }

コピーキャプチャ

\footnotesize \textcolor{lime}{ずんだもん:} 配列"b"のアドレスが、関数内の宣言部分と ラムダ式 内では異なっているのだ

\footnotesize \textcolor{pink}{四国めたん:} 明らかに配列がコピーされていますわね

\footnotesize \textcolor{lime}{ずんだもん:} ところで、 アロー演算子 ->戻り値の型 を指定していないのだが

\footnotesize \textcolor{pink}{四国めたん:} アロー演算子 ->とは違うのですが、それについては後で説明しますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに変数の全てをリストする場合には デフォルトコピーキャプチャ "="で代用できますわ

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [=]() {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
  }();
  return 0;
}

デフォルトコピーキャプチャ

\footnotesize \textcolor{lime}{ずんだもん:} お~、とりあえず ラムダ式 内で"a"も"b"も使えているのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} いずれにしても コピーキャプチャラムダ式 内ではコピーでしかないので、関数内の変数には、直接影響を与えることはできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

mutable

\footnotesize \textcolor{pink}{四国めたん:} ちなみに コピーキャプチャ は基本的にconstを付加された変数の扱いになりますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり処理内で変更することはできないと云うことか?

\footnotesize \textcolor{pink}{四国めたん:} ラムダ式 内で変数"a"に0を代入してみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [=]() {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
    a = 0;
  }();
  return 0;
}

コピーキャプチャのエラー

\footnotesize \textcolor{lime}{ずんだもん:} 「式は変更可能な左辺値である必要があります」と云うエラーが出ているのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{lime}{ずんだもん:} 変更できるようにはならないのか?

\footnotesize \textcolor{pink}{四国めたん:} 引数の括弧の直後にmutableキーワードを付加すると、 コピーキャプチャ された変数を処理内で変更できるようになりますわ

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [=]() mutable {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
    a = 0;
  }();
  return 0;
}

mutable

\footnotesize \textcolor{lime}{ずんだもん:} お~、変更してもエラーが出ていないのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} とはいえ、所詮はコピーなので、元の変数に影響を及ぼすことはできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} それは残念なのだ

参照キャプチャ

\footnotesize \textcolor{lime}{ずんだもん:} ラムダ式 内から関数内の変数に直接影響を与えることはできないのか?

\footnotesize \textcolor{pink}{四国めたん:} 実は 参照キャプチャ を使うことで、 ラムダ式 内での変更を元の関数の変数に反映することができるようになりますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} そのために、関数で宣言された変数を キャプチャリスト にリストする際に、変数名の前に"&"を付加しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 通常の変数の参照と同じような感じなのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは 参照キャプチャ を使った ラムダ式 を試してみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [&a, &b]() {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
    a = 0;
  }();
  std::cout << "aの値は" << a << std::endl;
  return 0;
 }

参照キャプチャ

\footnotesize \textcolor{lime}{ずんだもん:} 配列"b"のアドレスが ラムダ式 の内外で同じになっているのだ

\footnotesize \textcolor{pink}{四国めたん:} つまり配列"b"は、本体が ラムダ式 に渡されているのですわ

\footnotesize \textcolor{lime}{ずんだもん:} また、 ラムダ式 の内部で0を代入した"a"は、 ラムダ式 後に変更が反映されているのだ

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに変数の全てを 参照キャプチャ でリストする場合には、 デフォルト参照キャプチャ "&"を使いますわ

#include <iostream>

int main(int argc, char* argv[]) {
  int a = 1;
  int b[] = {0, 1, 2};
  std::cout << "bのアドレスは" << b << std::endl;
  [&]() {
    std::cout << "aの値は" << a << std::endl;
    std::cout << "bのアドレスは" << b << std::endl;
    a = 0;
  }();
  std::cout << "aの値は" << a << std::endl;
  return 0;
 }

デフォルト参照キャプチャ

\footnotesize \textcolor{lime}{ずんだもん:} お~、"a"も"b"も ラムダ式 内で普通に使えているのだ

キャプチャは混在できます

\footnotesize \textcolor{lime}{ずんだもん:} ところで キャプチャリストコピーキャプチャ参照キャプチャ を混在することができるのか?

\footnotesize \textcolor{pink}{四国めたん:} できますわね

\footnotesize \textcolor{pink}{四国めたん:} 例えば変数"x"と"y"があるとして、以下のような キャプチャリスト が可能ですわ

[&x, y]() { 処理 }    // (1)
[=, &x]() { 処理 }    // (2)
[&, y]() { 処理 }     // (3)

\footnotesize \textcolor{lime}{ずんだもん:} 説明をお願いするのだ

\footnotesize \textcolor{pink}{四国めたん:} まず、(1)の場合は"x"を 参照キャプチャ 、"y"を コピーキャプチャ としていますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむ

\footnotesize \textcolor{pink}{四国めたん:} 次に、(2)の場合は"x"を 参照キャプチャ 、"y"を含めた、"x"以外の変数を コピーキャプチャ としていますわね

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} 最後に、(3)の場合は"y"を コピーキャプチャ 、"x"を含めた、"y"以外の変数を 参照キャプチャ としていますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

引数

\footnotesize \textcolor{pink}{四国めたん:} つぎは ラムダ式 の引数についてですわね

\footnotesize \textcolor{lime}{ずんだもん:} 通常の関数の引数と同じなのか?

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} ポインタも参照も使うことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} 引数と キャプチャリスト の使い分けはどうすればいいのだ?

\footnotesize \textcolor{pink}{四国めたん:} 基本的に引数でも キャプチャリスト と概ね同様のことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむ

\footnotesize \textcolor{pink}{四国めたん:} ですので、 キャプチャリスト に慣れないあいだは無理に キャプチャリスト を使う必要はありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

戻り値

\footnotesize \textcolor{pink}{四国めたん:} 次に、戻り値の型の指定は、通常の関数とは異なりますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかにその通りなのだ

\footnotesize \textcolor{pink}{四国めたん:} 指定方法は、引数に続いて 後置戻り型指定子 (Trailing Return Type Specifier)->の後に型を明示しますわ

\footnotesize \textcolor{lime}{ずんだもん:} うん? アロー演算子 ->とは違うのか?

\footnotesize \textcolor{pink}{四国めたん:} 記号としては同じですが、意味的には異なりますわね

\footnotesize \textcolor{lime}{ずんだもん:} どのような意味があるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 戻り値の型を関数の宣言の最後に指定するための指定子ですわね

\footnotesize \textcolor{lime}{ずんだもん:} よくわからないのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず具体例を見てみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int c = []() -> int { return 3; }();
  std::cout << "cの値は" << c << std::endl;
  return 0;
 }

戻り値

\footnotesize \textcolor{pink}{四国めたん:} なお、戻り値がある ラムダ式 は実行後に戻り値を変数に代入するなど、利用することができますわ

\footnotesize \textcolor{lime}{ずんだもん:} それは通常の関数と同じなのだ

\footnotesize \textcolor{lime}{ずんだもん:} ところで戻り値がない ラムダ式 の場合は、型としてvoidを指定すればいいのか?

\footnotesize \textcolor{pink}{四国めたん:} いいえ、voidを指定するのではなく、 後置戻り型指定子 ->戻り値の型 を記述しないようにしますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、前に->戻り値の型 を記述していなかったのは、そういうわけなのだな

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} また、戻り値から 戻り値の型 が明確な場合には、 後置戻り型指定子 ->戻り値の型 を省略できますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、それは便利なのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず例を見てみましょう

#include <iostream>

int main(int argc, char* argv[]) {
  int c = []() { return 3; }();
  std::cout << "cの値は" << c << std::endl;
  return 0;
 }

戻り値の省略

\footnotesize \textcolor{lime}{ずんだもん:} returnで整数を返しているので、戻り値の型を省略しても問題なく動作するのだ

\footnotesize \textcolor{pink}{四国めたん:} 最近のコンパイラは優秀ですわね

処理

\footnotesize \textcolor{pink}{四国めたん:} 最後は処理部分ですわ

\footnotesize \textcolor{lime}{ずんだもん:} ラムダ式 の処理部分は、通常の関数と同じではないのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、ただ キャプチャリスト でリストされた変数を使えることが異なっていますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れ様でした

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} 以上で ラムダ式 について一旦終了しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 一旦?

\footnotesize \textcolor{pink}{四国めたん:} ラムダ式 は関数名に相当する部分がないので、そのままでは再利用ができませんわ

\footnotesize \textcolor{lime}{ずんだもん:} このままでは定義と同時に実引数を指定して使うことしかできないのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは不便なので、次回は再利用できるようにする方法についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} よろしくなのだ

Discussion