サイトアイコン Embarcadero RAD Studio, Delphi, & C++Builder Blogs

C++BuilderによるC++ラムダ式 入門

この記事は、Ummul Khair氏のブログの抄訳です

ラムダ式(lambda expressions)は、それが使用される時点で、無名関数またはクロージャを定義します。ラムダ式は名前のない関数と考えることができます(そのため「匿名」と呼ばれています)。 ラムダ式はコードを明確かつ簡潔に記述でき、関数オブジェクト(Functor)のような外部のメソッドを参照する代わりに、定義されたインラインで動作を確認することができます。ラムダ式は通常、以下のような形式をとります。

[crayon-6743c857cf900896917023/]

ラムダ式は、モダンなC++言語機能の一例です。このブログでは、ラムダ式、ラムダ式の一部、そしてコードを書くときにラムダ式をどのように使用するか解説いたします。

ラムダ式を構成するパーツ

ラムダ式のパーツには、キャプチャ句(Capture Clause)、パラメータリスト(オプション)、戻り値の型(オプション)、そして関数本体(body of function)があります(ただし、一部のパーツは必須ではありません)。

上図は、varパラメータとラムダ式のスコープ外の変数(externalVariable)を乗算し、int型の結果を返すシンプルなラムダ式です。

キャプチャ句(capture clause)

キャプチャ句は、ラムダが宣言されているスコープ内の変数を、コピーまたは参照によってキャプチャし、ラムダの関数本体で使用可能にするためコンパイラへ指示する目的で使用されます。キャプチャ句は関数の要件を指定するためにさまざまな方法で使用できます。

キャプチャ句はラムダの冒頭部分で、角括弧[]で囲まれています。

ここでは、キャプチャ句を宣言する方法をいくつか紹介します。

1. [](){}は、なにもキャプチャせず、パラメータを受け取らず、なにも処理せず、戻り値の型がvoidである関数オブジェクトを意味しています。

2. [=](){}は、関数の定義で使用されるすべての変数を値によってキャプチャすることを指定します。これにより、値によるキャプチャがデフォルトのキャプチャ型となります。

3. [&](){}は、関数の定義で使用されるすべての変数を参照によってキャプチャすることを指定します。これにより、参照によるキャプチャがデフォルトのキャプチャ型となります。

4. C++11 で導入された *this のキャプチャは、現在のオブジェクトのコピーをキャプチャするか、またはオブジェクト自体をキャプチャすることを目的としています。C++17 では、[*this] を値でキャプチャする機能が導入されました。この値で [*this] をキャプチャすることは、非静的なメンバ関数からラムダ式が非同期にディスパッチされるようなシナリオでは重要です。このポインタは、関数の実行時には有効ではない可能性があります。そのため、*thisを値でキャプチャできることが重要です。この新機能については、Lambda Capture of *this by Value as [=,*this]をご覧ください。

5. [=][&]の両方を1つのキャプチャ句の中で併用することができます。例えば、[=, &var]は、すべての変数をコピーでキャプチャしますが、変数 “var “を参照によってキャプチャできます。

6. 同様に、参照によってすべての変数をキャプチャし、コピーによってキャプチャする特定の変数を指定することもできます。例えば、[&, var]は、すべての変数を参照でキャプチャでき、変数 “var“をコピーによってキャプチャできます。

7. 一部のキャプチャ句の定義では、冗長性が原因でエラーが発生することがあります。たとえば、[&, &var]のようなキャプチャ句は、すでにすべての変数を参照によってキャプチャすることを指定した後で、varを参照によってキャプチャすべきだと再指定しているので、問題があります。

パラメータリスト(Parameters List)

ラムダのパラメータリストは、他のメソッドのパラメータリストと同じように、中括弧()内のパラメータのセットです。ラムダ式の入力パラメータは、ラムダ式を定義する際に列挙することができますが、必須ではありません。

戻り値の型(Return Type)

多くの場合、戻り値の型はコンパイラによって推論できるため指定する必要はありません。 例えば、return文が1つの場合、コンパイラは簡単に戻り値の型推論ができますが、より複雑なラムダの場合は、明示的に指定する必要があるかもしれません。

本体(Body)

ラムダ式の本体には、通常の関数と同じようにコードを記述します。

ラムダ式の様々な書き方

ここでは、C++Builderを使用したラムダ式のいくつかのコード例を紹介いたします。

1. [ captures ] ( params ) -> ret { body }

このメソッドでは、キャプチャ句を使用して、いくつかのパラメータと戻り値の型を指定しています。

サンプルコード:

[crayon-6743c857cf90e319475494/]

上記コードは、すべての変数がvalueによってコピーされ、関数の戻り値の型が int であることを示しています。中括弧で囲まれたステートメントは、関数の本体を表しています。ラムダの外部の変数(valueはコピーによってキャプチャされているので、const値として利用可能)と通常のパラメータ(a)の両方を使用していることに注意してください。

このラムダでは、コンパイラが戻り値の型推論することができますが、ここではintを指定しています。

2. [ captures ] ( params ) { body }

このメソッドでは、キャプチャ句を使用し、いくつかのパラメータを指定しています。戻り値の型推論されなければなりません。

サンプルコード:

[crayon-6743c857cf913906534148/]

上記のコードは、すべての変数が参照可能であること(書き戻すことができることを意味します)と、関数の戻り値の型推論できることを示しています。中括弧で囲まれたステートメントは、関数の本体を表しています。

通常、キャプチャされた変数は const 値変数です。これらはコピーであり、変更することはできません。ここでは、valueが参照によってキャプチャされているので、ラムダの中から「value」の値を変更することができます。

3. [ captures ] { body }

このメソッドでは、キャプチャ句を使用しており、パラメータを除外しています。戻り値の型推論されるべきです。 

サンプルコード:

[crayon-6743c857cf916594947044/]

ラムダ式は、キャプチャ句のみを指定して、関数の本体を定義することでも記述できます。戻り値の型が指定されていない場合は、推論されます。

C++17での変更点

C++17 では、いくつかの機能のアップデートが行われました。 C++17 でラムダ式に加えられた変更点には、次のような項目があります。

Constexprラムダ式の導入: ラムダ式をconstexprとして宣言できるようになりました。constexprキーワードを使用して、コンパイル時に式を実行するように指定できます。 ラムダ式がconstexprとして宣言されている場合、ラムダ式は特定の規則に従う必要があることに注意してください。式がconstexprとして宣言されている場合、式の本体にはconstexpr以外コードを含めるべきではありません。たとえば、メモリを動的に割り当てるべきではありません。

ラムダ式での [*this] のキャプチャ: ラムダ式での [*this] の値によるキャプチャはC++17で導入されました。C++17 より前のバージョンでは、この機能は許可されていなかったため、コピーを作成してキャプチャする必要がありました(エラーが発生しやすくなります)。

結論として、ラムダ式は、関数を書く代わりに、すっきりとした簡潔な方法を提供します。キャプチャ句やパラメータ(オプション)を利用することで、ユーザーの要件に基づいてさまざまな方法で宣言できます。さらに、戻り値の型推論したり、ラムダ式をconstexprとして宣言することもできます。

関連情報

このブログで紹介した内容以外にもモダンなC++言語仕様でサポートされている機能は多数あります。エンバカデロのブログでも過去に「C++Builderで使用方法を学ぶ」シリーズとして、いくつか紹介しています。もしご興味がありましたら、ご参照ください。

C++Builder 10.4では、最新のC++17の言語仕様をサポートしており、C++17の新機能もすぐに試せます。製品の詳細は、こちらを参照してください。

モバイルバージョンを終了