ラムダ式の構文
この記事では、ラムダ式の構文と構造体要素を説明します。 ラムダ式の詳細については、「C++ でのラムダ式」を参照してください。
ラムダ式の文法
ISO C++11 標準による次の定義はラムダ式の文法を示しています (opt という下付き文字が付いた項目は省略可能です)。
lambda-introducer lambda-declaratoropt compound-statement
これらの構文の構成要素はさらに次のように分解されます。
lambda-introducer:
[ lambda-captureopt ]
lambda-capture:
capture-default
capture-list
capture-default , capture-list
capture-default:
&
=
capture-list:
capture ...opt
capture-list , capture ...opt
capture:
identifier
& identifier
this
lambda-declarator:
( parameter-declaration-clause ) mutableopt
exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt
Visual Studio では C++11 標準ラムダ式の構文と機能をサポートしています。ただし次の点は除きます。
ラムダは、他のすべてのクラスと同様、移動コンストラクターと移動代入演算子を自動的に生成しません。 右辺値参照動作のサポートの詳細については、「C++11 の機能 (Modern C++) のサポート」の「右辺値参照」セクションを参照してください。
オプションの attribute-specifier-seq はこのバージョンではサポートされていません。
Visual Studio には C++11 標準ラムダ機能に加えて、次の機能が含まれています。
ステートレス ラムダ。任意の呼び出し規約に従った関数ポインターへのオムニ変換が可能です。
ラムダ本体の自動推測される戻り値の型は、すべての return ステートメントが同じ型である限り、{ return expression; } よりも複雑です (この点は提案されている C++14 標準に含まれています)。
ラムダ式のプロパティ
この図は、文法を例に対応付けたものです。
lambda-introducer (capture 句とも呼ばれます)
lambda declarator (パラメーター リストとも呼ばれます)
mutable (変更可能な指定とも呼ばれます)
exception-specification (例外の指定とも呼ばれます)
trailing-return-type (戻り値の型とも呼ばれます)
compound-statement (ラムダ式の本体とも呼ばれます)
capture 句
ラムダ式は、基本的にクラス、コンストラクター、関数呼び出し演算子です。 クラスを定義するときと同様に、ラムダでは、結果として生成されるオブジェクトが変数のキャプチャを値によって行うか、参照によって行うか、またはまったく行わないかを決める必要があります。 ラムダ式がローカル変数と関数パラメーターにアクセスする必要がある場合、それらの変数とパラメーターはキャプチャされる必要があります。 capture 句 (標準構文内の lambda-introducer) は、ラムダ式の本体が外側のスコープ内の変数に値または参照でアクセスできるかどうかを指定します。 アンパサンド (&) プレフィックス付きの変数は参照でアクセスされ、アンパサンド プレフィックスなしの変数は値でアクセスされます。
空のキャプチャ句 [ ] は、ラムダ式の本体が外側のスコープ内の変数にアクセスしないことを示します。
既定のキャプチャ モード (標準構文内の capture-default) を使用して、未指定の変数を値または参照のどちらかでキャプチャすることができます。 capture 句の最初の要素として & または = を使用することで、既定のキャプチャ モードを指定できます。 & 要素により、ラムダ式の本体は未指定の変数に参照でアクセスします。 = 要素により、ラムダ式の本体は未指定の変数に値でアクセスします。 たとえば、ラムダ式の本体が参照によって外部変数 total にアクセスし、値によって外部変数 factor にアクセスする場合、次の capture 句は同じ結果になります。
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
capture-default については一般的な誤解があります。スコープ内のすべての変数は、ラムダで使用されているかどうかにかかわらず、キャプチャされるというものです。 そういうことはありません。capture-default を使用すると、ラムダで記述されている変数のみがキャプチャされます。
capture 句に capture-default & が含まれている場合、その capture 句の capture に含まれるいずれの identifier も & identifier 形式にすることはできません。 同様に、capture 句に capture-default = が含まれている場合、その capture 句のいずれの capture も = identifier 形式にすることはできません。 識別子または this を capture 句で複数回使用することはできません。 次のコードでは、そのいくつかの例を示しています。
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[i, i]{}; // ERROR: i repeated
}
次の可変個引数テンプレートの例に示しているように、capture の後ろに省略記号を付けると、パックの展開を意味します。
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
クラス メソッドの本体でラムダ式を使用できます。 外側のクラスのメソッドおよびデータ メンバーへのアクセスを提供するには、this ポインターを capture 句に渡します。 クラス メソッドのラムダ式を使用する方法の例については、「ラムダ式の例」の「例: メソッドでのラムダ式の使用」を参照してください。
capture 句を使用するとき、特にマルチスレッドでラムダを使用するときは、次の重要点に注意することをお勧めします。
参照キャプチャは外部の変数を変更するために使用できますが、値キャプチャはその目的には使用できません (mutable ではそのコピーを変更できても元の変数は変更できません)。
参照キャプチャでは更新が外部の変数に反映されますが、値キャプチャでは反映されません。
参照キャプチャは有効期間に依存しますが、値キャプチャは依存しません。
[パラメーター リスト]
パラメーター リスト (標準構文内の lambda declarator) は必要に応じて指定でき、関数のパラメーター リストに似ています。
ラムダ式は、引数として別のラムダ式を受け取ることができます。 詳細については、「ラムダ式の例」の「上位ラムダ式」を参照してください。
パラメーター リストはオプションなので、ラムダ式に引数を渡さず、lambda-declarator: に exception-specification、trailing-return-type、または mutable を含めない場合、空のかっこを省略できます。
変更可能な指定
通常、ラムダの関数呼び出し演算子は const 値ですが、mutable キーワードを使用することで無効になります。 これによりデータ メンバーが変更可能になることはありません。 mutable の指定により、ラムダ式の本体で値キャプチャされる変数を変更できるようになります。 この記事の後半にあるいくつかの例で mutable の使用方法を示しています。
例外の指定
throw() 例外の指定を使用して、ラムダ式が例外をスローしないことを示すことができます。 通常の関数の場合と同様、次の例に示すように、ラムダ式で throw() 例外の指定を宣言し、ラムダの本体で例外をスローする場合、Visual C++ コンパイラは警告 C4297 を生成します。
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() throw() { throw 5; }();
}
詳細については、「例外の仕様」を参照してください。
戻り値の型
ラムダ式の戻り値の型は自動的に推測されます。 trailing-return-type を指定しなければ、auto キーワードを表現する必要はありません。 trailing-return-type は、通常のメソッドまたは関数の戻り値の型の部分に似ています。 ただし、戻り値の型はパラメーター リストに従い、戻り値の型の前に trailing-return-type キーワード -> を含める必要があります。
ラムダ式の本体に return ステートメントが 1 つだけ含まれるか、ラムダ式が値を返さない場合は、ラムダ式の return-type 部分を省略できます。 ラムダの本体が単一の return ステートメントで構成される場合、コンパイラは return 式の型から戻り値の型を推測します。 それ以外の場合、コンパイラは戻り値の型が void であると推測します。 この原理を説明する次のコード例について考えてみましょう。
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list is not valid
ラムダ式は、戻り値として別のラムダ式を作成できます。 詳細については、「ラムダ式の例」の「上位ラムダ式」を参照してください。
ラムダ式の本体
ラムダ式の本体 (標準構文内の compound-statement) には、通常のメソッドや関数の本体と同じものを含めることができます。 通常の関数の本体もラムダ式の本体も次の種類の変数にアクセスできます。
パラメーター
ローカル宣言変数
クラスのデータ メンバー。クラス内で宣言され、this がキャプチャされる場合
静的ストレージ存続期間の任意の変数 (たとえば、グローバル変数)
また、ラムダ式は外側のスコープからキャプチャする変数にアクセスできます。 変数は、ラムダ式の capture 句にある場合は、明示的にキャプチャされます。 それ以外の場合、変数は暗黙的にキャプチャされます。 ラムダ式の本体は、既定のキャプチャ モードを使用して、暗黙的にキャプチャされる変数にアクセスします。
次の例には、変数 n を明示的に値でキャプチャし、変数 m を暗黙的に参照でキャプチャするラムダ式が含まれています。
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
Output:
変数 n は値でキャプチャされるため、ラムダ式への呼び出しの後も値は 0 のまま残ります。 mutable の指定により、n をラムダ内で変更できるようになります。
ラムダ式は自動ストレージ存続期間がある変数のみキャプチャできますが、ラムダ式の本体では静的ストレージ存続期間がある変数を使用できます。 次の例では、generate 関数とラムダ式を使用して、vector オブジェクトの各要素に値を代入します。 ラムダ式は、静的変数を変更して次の要素の値を生成します。
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
詳細については、「生成」を参照してください。
次のコード例では、前の例の関数を使用し、STL アルゴリズム generate_n を使用するラムダ式の例を追加しています。 このラムダ式は vector オブジェクトの要素を前の 2 つの要素の合計に代入します。 ラムダ式が値でキャプチャする外部変数 x と y のコピーをラムダ式が変更できるように、mutable キーワードを使用しています。 ラムダ式は元の変数 x および y を値でキャプチャするため、それらの値はラムダの実行後も 1 のまま残ります。
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
Output:
詳細については、「generate_n」を参照してください。
Microsoft 固有の修飾子
__declspec などの Microsoft 固有の修飾子を使用する場合、ラムダ式内の parameter-declaration-clause の直後に挿入できます。次に例を示します。
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
修飾子がラムダでサポートされているかどうかを判別するには、ドキュメントの「Microsoft 固有の修飾子」セクションを参照してください。