ラムダ式 (C++11 以降)
クロージャ (スコープ内の変数をキャプチャできる名前のない関数オブジェクト) を構築します。
目次 |
[編集] 構文
[編集] 明示的なテンプレートパラメータリストを持たないラムダ式 (非ジェネリックの可能性あり)
[captures ] front-attr (optional) (params ) specs (optional) except (optional)back-attr (optional) trailing (optional) requires (optional) contract-specs (optional) { body } |
(1) | ||||||||
[captures ] { body } |
(2) | (C++23まで) | |||||||
[captures ] front-attr (optional) trailing (optional) contract-specs (optional) { body } |
(2) | (C++23から) | |||||||
[captures ] front-attr (optional) exceptback-attr (optional) trailing (optional) contract-specs (optional) { body } |
(3) | (C++23から) | |||||||
[captures ] front-attr (optional) specs except (optional)back-attr (optional) trailing (optional) contract-specs (optional) { body } |
(4) | (C++23から) | |||||||
[編集] 明示的なテンプレートパラメータリストを持つラムダ式 (常にジェネリック) (C++20 以降)
[captures ] <tparams > t-requires (optional)front-attr (optional) (params ) specs (optional) except (optional)back-attr (optional) trailing (optional) requires (optional) contract-specs (optional) { body } |
(1) | ||||||||
[captures ] <tparams > t-requires (optional) { body } |
(2) | (C++23まで) | |||||||
[captures ] <tparams > t-requires (optional)front-attr (optional) trailing (optional) contract-specs (optional) { body } |
(2) | (C++23から) | |||||||
[captures ] <tparams > t-requires (optional) front-attr (optional) exceptback-attr (optional) trailing (optional) contract-specs (optional) { body } |
(3) | (C++23から) | |||||||
[captures ] <tparams > t-requires (optional) front-attr (optional) specs except (optional)back-attr (optional) trailing (optional) contract-specs (optional) { body } |
(4) | (C++23から) | |||||||
[編集] 説明
| captures | - | キャプチャするエンティティを指定します。 | ||||||||||||
| tparams | - | 空ではないカンマ区切りのテンプレートパラメータリスト。ジェネリックラムダのテンプレートパラメータに名前を付けるために使用されます (以下のClosureType::operator()を参照)。 | ||||||||||||
| t-requires | - | tparamsに制約を追加します。
| ||||||||||||
| front-attr | - | (C++23 以降) 属性指定子シーケンスは、クロージャ型のoperator()に適用されます (したがって、[[noreturn]]属性を使用できます)。 | ||||||||||||
| params | - | クロージャ型のoperator()のパラメータリスト。
| ||||||||||||
| specs | - | 次の指定子のリスト。各指定子は、各シーケンスで最大1回まで許可されます。
| ||||||||||||
| except | - | クロージャ型のoperator()に対する動的例外指定または(C++20 まで)noexcept指定子を提供します。 | ||||||||||||
| back-attr | - | 属性指定子シーケンスは、クロージャ型のoperator()の型に適用されます (したがって、[[noreturn]]属性は使用できません)。 | ||||||||||||
| trailing | - | -> ret。ここでretは戻り値の型を指定します。 | ||||||||||||
| 要求するようになった | - | (C++20 以降) クロージャ型のoperator()に制約を追加します。 | ||||||||||||
| contract-specs | - | (C++26 以降) クロージャ型のoperator()に対する関数契約指定子のリスト。 | ||||||||||||
| body | - | 関数本体。 |
|
パラメータの型として |
(C++14以降) |
bodyの先頭では、__func__変数が暗黙的に定義され、そのセマンティクスはここで説明されています。
[編集] クロージャ型
ラムダ式は、**クロージャ型**として知られる、一意な名前のない非共用体非集約体クラス型のprvalue式であり、(ADLの目的で) ラムダ式を含む最小のブロックスコープ、クラススコープ、または名前空間スコープで宣言されます。
|
クロージャ型は、capturesが空である場合にのみ、構造型です。 |
(C++20以降) |
クロージャ型は次のメンバを持ち、それらは明示的にインスタンス化、明示的に特殊化、または(C++14 以降)フレンド宣言で名前を付けることはできません。
ClosureType::operator()(params)
| ret operator()(params) { body } |
(static および const が存在する場合あり、以下を参照) | |
| template<template-params> ret operator()(params) { body } |
(C++14以降) (ジェネリックラムダ、static および const が存在する場合あり、以下を参照) |
|
呼び出されたときにラムダ式の本体を実行します。変数を参照するときは、キャプチャされたコピー (コピーでキャプチャされたエンティティの場合) または元のオブジェクト (参照でキャプチャされたエンティティの場合) を参照します。
operator()のパラメータリストは、提供されていればparamsであり、そうでなければ空のパラメータリストです。
operator()の戻り値の型は、trailing で指定された型です。
trailingが指定されていない場合、operator()の戻り値の型は自動的に推論されます。[1]
ラムダ指定子でキーワードmutableが使用されていない場合、または明示的なオブジェクトパラメータが存在する場合を除き(C++23 以降)、operator()のcv修飾子はconstであり、コピーでキャプチャされたオブジェクトは、このoperator()内から変更できません。明示的なconst修飾子は許可されません。operator()は決して仮想ではなく、volatile修飾子を持つことはできません。
|
operator()は、constexpr関数の要件を満たす場合、常にconstexprです。ラムダ指定子でキーワードconstexprが使用された場合もconstexprです。 |
(C++17以降) |
|
operator()は、ラムダ指定子でキーワードconstevalが使用された場合、即時関数です。 |
(C++20以降) |
|
operator()は、ラムダ指定子でキーワードstaticが使用された場合、静的メンバ関数です。 paramsに明示的なオブジェクトパラメータが含まれている場合、operator()は明示的オブジェクトメンバ関数です。 |
(C++23から) |
|
params内の型がautoと指定されている各パラメータについて、出現順に架空のテンプレートパラメータがtemplate-paramsに追加されます。対応するparamsの関数メンバが関数パラメータパックである場合、架空のテンプレートパラメータはパラメータパックである可能性があります。 |
(C++14以降) |
|
ラムダ定義が明示的なテンプレートパラメータリストを使用する場合、そのテンプレートパラメータリストがoperator()で使用されます。params内の型がautoと指定されている各パラメータについて、追加の架空のテンプレートパラメータがそのテンプレートパラメータリストの末尾に追加されます。 |
(C++20以降) |
ラムダ式上の例外指定exceptは、operator()に適用されます。
名前探索、thisポインタの型と値の決定、および非静的クラスメンバへのアクセスの目的のために、クロージャ型のoperator()の本体は、ラムダ式のコンテキストで考慮されます。
struct X { int x, y; int operator()(int); void f() { // the context of the following lambda is the member function X::f [=]() -> int { return operator()(this->x + y); // X::operator()(this->x + (*this).y) // this has type X* }; } };
宙に浮いた参照
非参照エンティティが参照によって暗黙的または明示的にキャプチャされ、そのエンティティの寿命が終了した後にクロージャオブジェクトのoperator()が呼び出されると、未定義の動作が発生します。C++のクロージャは、参照によってキャプチャされたオブジェクトの寿命を延長しません。
現在の*thisオブジェクトがthisを介してキャプチャされた場合も同様に、そのオブジェクトの寿命に適用されます。
- ↑ 関数戻り値型推論は C++14 で導入されましたが、そのルールは C++11 のラムダ戻り値型推論で利用可能です。
ClosureType::operator ret(*)(params)()
| キャプチャなし非ジェネリックラムダ |
||
using F = ret(*)(params); operator F() const noexcept; |
(C++17まで) | |
| using F = ret(*)(params); constexpr operator F() const noexcept; |
(C++17以降) | |
| キャプチャなしジェネリックラムダ |
||
template<template-params> using fptr_t = /* see below */; template<template-params> |
(C++14以降) (C++17まで) |
|
| template<template-params> using fptr_t = /* see below */; template<template-params> |
(C++17以降) | |
このユーザー定義変換関数は、ラムダ式にcaptures がなく、かつ明示的なオブジェクトパラメータがない場合のみ定義されます(C++23 以降)。これは、クロージャオブジェクトのpublic、constexpr、(C++17 以降)非仮想、非explicit、const noexceptメンバ関数です。
|
この関数は、関数呼び出し演算子 (またはジェネリックラムダの場合は特殊化) が即時関数である場合、即時関数です。 |
(C++20以降) |
|
ジェネリックなキャプチャなしラムダは、operator()と同じ架空のテンプレートパラメータリストを持つユーザー定義変換関数テンプレートを持ちます。 |
(C++14以降) |
|
変換関数によって返される値は、呼び出されたときに、クロージャ型のデフォルト構築されたインスタンスに対してクロージャ型の関数呼び出し演算子を呼び出すのと同じ効果を持つ、C++の言語リンケージを持つ関数へのポインタです。 |
(C++14まで) |
|
変換関数 (テンプレート) によって返される値は、呼び出されたときに、C++の言語リンケージを持つ関数へのポインタで、その効果は以下の通りです。
|
(C++14以降) (C++23まで) |
|
変換関数 (テンプレート) によって返される値は、 |
(C++23から) |
|
この関数は、関数呼び出し演算子 (またはジェネリックラムダの場合は特殊化) が constexpr である場合、constexpr です。 クロージャオブジェクトのoperator()が非投擲例外指定を持つ場合、この関数によって返されるポインタはnoexcept関数のポインタ型を持ちます。 |
(C++17以降) |
ClosureType::ClosureType()
| ClosureType() = default; |
(C++20以降) (キャプチャが指定されていない場合のみ) |
|
| ClosureType(const ClosureType&) = default; |
||
| ClosureType(ClosureType&&) = default; |
||
|
クロージャ型はDefaultConstructibleではありません。クロージャ型にはデフォルトコンストラクタがありません。 |
(C++20まで) |
|
capturesが指定されていない場合、クロージャ型にはデフォルト化されたデフォルトコンストラクタがあります。それ以外の場合、デフォルトコンストラクタはありません (これは、capture-default があっても、実際に何もキャプチャしない場合を含みます)。 |
(C++20以降) |
コピーコンストラクタとムーブコンストラクタは、デフォルト化されたものとして宣言され、コピーコンストラクタおよびムーブコンストラクタの通常のルールに従って暗黙的に定義されることがあります。
ClosureType::operator=(const ClosureType&)
| ClosureType& operator=(const ClosureType&) = delete; |
(C++20まで) | |
| ClosureType& operator=(const ClosureType&) = default; ClosureType& operator=(ClosureType&&) = default; |
(C++20以降) (キャプチャが指定されていない場合のみ) |
|
| ClosureType& operator=(const ClosureType&) = delete; |
(C++20以降) (それ以外の場合) |
|
|
コピー代入演算子はdeletedとして定義されます (ムーブ代入演算子は宣言されません)。クロージャ型はCopyAssignableではありません。 |
(C++20まで) |
|
capturesが指定されていない場合、クロージャ型はデフォルト化されたコピー代入演算子とデフォルト化されたムーブ代入演算子を持ちます。それ以外の場合、deletedコピー代入演算子を持ちます (これは、capture-default があっても、実際に何もキャプチャしない場合を含みます)。 |
(C++20以降) |
ClosureType::~ClosureType()
| ~ClosureType() = default; |
||
デストラクタは暗黙的に宣言されます。
ClosureType::Captures
| T1 a; T2 b; |
||
ラムダ式が何かをコピーでキャプチャする場合 (暗黙的にキャプチャ句[=]を使用するか、または&文字を含まないキャプチャを明示的に使用する場合、例: [a, b, c])、クロージャ型には、そのようにキャプチャされたすべてのエンティティのコピーを保持する、指定されていない順序で宣言された名前のない非静的データメンバが含まれます。
初期化子を持たないキャプチャに対応するデータメンバは、ラムダ式が評価されるときに直接初期化されます。初期化子を持つキャプチャに対応するデータメンバは、初期化子が必要とするように初期化されます (コピー初期化または直接初期化のいずれか)。配列がキャプチャされた場合、配列要素はインデックスの昇順で直接初期化されます。データメンバが初期化される順序は、それらが宣言された順序です (これは指定されていません)。
各データメンバの型は、対応するキャプチャされたエンティティの型です。ただし、エンティティが参照型である場合を除きます (その場合、関数への参照は参照された関数へのlvalue参照としてキャプチャされ、オブジェクトへの参照は参照されたオブジェクトのコピーとしてキャプチャされます)。
参照によってキャプチャされるエンティティ ( capture-default [&]を使用するか、&文字を使用する場合、例: [&a, &b, &c]) については、クロージャ型に追加のデータメンバが宣言されるかどうかは指定されていませんが、そのような追加のメンバはLiteralTypeを満たさなければなりません(C++17 以降)。
|
ラムダ式は、未評価の式、テンプレート引数、エイリアス宣言、typedef宣言、および関数本体と関数のデフォルト引数を除く、関数 (または関数テンプレート) 宣言のどこにも許可されません。 |
(C++20まで) |
[編集] ラムダキャプチャ
capturesは、ラムダ関数本体内からアクセス可能な外部変数を定義します。その構文は次のように定義されます。
| capture-default | (1) | ||||||||
| capture-list | (2) | ||||||||
capture-default , capture-list |
(3) | ||||||||
| capture-default | - | & または = のいずれか |
| capture-list | - | カンマ区切りのcapture のリスト |
captureの構文は次のように定義されます。
| identifier | (1) | ||||||||
identifier ... |
(2) | ||||||||
| identifier initializer | (3) | (C++14以降) | |||||||
& identifier |
(4) | ||||||||
& identifier ... |
(5) | ||||||||
& identifier initializer |
(6) | (C++14以降) | |||||||
this
|
(7) | ||||||||
* this |
(8) | (C++17以降) | |||||||
... identifier initializer |
(9) | (C++20以降) | |||||||
& ... identifier initializer |
(10) | (C++20以降) | |||||||
capture-defaultが&の場合、後続の単純なキャプチャは&で始まってはなりません。
struct S2 { void f(int i); }; void S2::f(int i) { [&] {}; // OK: by-reference capture default [&, i] {}; // OK: by-reference capture, except i is captured by copy [&, &i] {}; // Error: by-reference capture when by-reference is the default [&, this] {}; // OK, equivalent to [&] [&, this, i] {}; // OK, equivalent to [&, i] }
capture-defaultが=の場合、後続の単純なキャプチャは&で始まらなければなりません、または*thisでなければなりません(C++17 以降) またはthisでなければなりません(C++20 以降)。
struct S2 { void f(int i); }; void S2::f(int i) { [=] {}; // OK: by-copy capture default [=, &i] {}; // OK: by-copy capture, except i is captured by reference [=, *this] {}; // until C++17: Error: invalid syntax // since C++17: OK: captures the enclosing S2 by copy [=, this] {}; // until C++20: Error: this when = is the default // since C++20: OK, same as [=] }
どのキャプチャも1回しか出現できず、その名前はどのパラメータ名とも異なっている必要があります。
struct S2 { void f(int i); }; void S2::f(int i) { [i, i] {}; // Error: i repeated [this, *this] {}; // Error: "this" repeated (C++17) [i] (int i) {}; // Error: parameter and capture have the same name }
ラムダ式は、変数をキャプチャせずに使用できます。その変数が
ラムダ式は、変数をキャプチャせずにその値を読み取ることができます。その変数が
- const非volatile整数型または列挙型であり、定数式で初期化されている場合、または
- constexprであり、mutableメンバを持たない場合。
現在のオブジェクト (*this) は、いずれかのキャプチャデフォルトが存在する場合、暗黙的にキャプチャできます。暗黙的にキャプチャされた場合、キャプチャデフォルトが=であっても、常に参照でキャプチャされます。キャプチャデフォルトが=の場合の*thisの暗黙的なキャプチャは非推奨です。(C++20 以降)
次の条件のいずれかを満たすラムダ式のみが、初期化子なしのcapture-defaultまたはcaptureを持つことができます。
- その最も内側の囲むスコープがブロックスコープである。
- デフォルトメンバ初期化子内に出現し、その最も内側の囲むスコープが対応するクラススコープである。
| (C++26以降) |
そのようなラムダ式の場合、*到達スコープ*は、最も内側の囲む関数 (およびそのパラメータ) を含む、囲むスコープのセットとして定義されます。これには、このラムダがネストされている場合のネストされたブロックスコープと囲むラムダのスコープが含まれます。
初期化子を持たないキャプチャ (thisキャプチャを除く) のidentifierは、ラムダの*到達スコープ*内で通常の非修飾名前探索を使用して探索されます。探索の結果は、到達スコープで宣言された自動記憶域期間を持つ変数でなければなりません、または対応する変数がそのような要件を満たす構造化束縛でなければなりません(C++20 以降)。エンティティは*明示的にキャプチャ*されます。
|
*初期化キャプチャ*と呼ばれる初期化子を持つキャプチャは、型指定子
これは、x = std::move(x)のようなキャプチャでムーブオンリー型をキャプチャするために使用されます。 これにより、&cr = std::as_const(x)などを使用して、const参照でキャプチャすることも可能になります。 int x = 4; auto y = [&r = x, x = x + 1]() -> int { r += 2; return x * x; }(); // updates ::x to 6 and initializes y to 25. |
(C++14以降) |
capturesがcapture-defaultを持ち、囲むオブジェクト (thisまたは*thisとして)、またはラムダ本体でodr-usableな自動変数、または対応する変数が自動記憶域期間を持つ構造化束縛(C++20 以降)を明示的にキャプチャしない場合、そのエンティティが式内の潜在的に評価される式内で名前が付けられている場合 (非静的クラスメンバの使用前に暗黙的なthis->が追加される場合を含む)、エンティティを*暗黙的にキャプチャ*します。
暗黙的なキャプチャを決定する目的で、typeidは決してそのオペランドを未評価にすると見なされません。
|
エンティティは、ラムダ本体のインスタンス化後に破棄された文内で名前が付けられているだけでも、暗黙的にキャプチャされる可能性があります。 |
(C++17以降) |
void f(int, const int (&)[2] = {}) {} // #1 void f(const int&, const int (&)[1]) {} // #2 struct NoncopyableLiteralType { constexpr explicit NoncopyableLiteralType(int n) : n_(n) {} NoncopyableLiteralType(const NoncopyableLiteralType&) = delete; int n_; }; void test() { const int x = 17; auto l0 = []{ f(x); }; // OK: calls #1, does not capture x auto g0 = [](auto a) { f(x); }; // same as above auto l1 = [=]{ f(x); }; // OK: captures x (since P0588R1) and calls #1 // the capture can be optimized away auto g1 = [=](auto a) { f(x); }; // same as above auto ltid = [=]{ typeid(x); }; // OK: captures x (since P0588R1) // even though x is unevaluated // the capture can be optimized away auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2] = {}; f(x, selector); // OK: is a dependent expression, so captures x }; auto g3 = [=](auto a) { typeid(a + x); // captures x regardless of // whether a + x is an unevaluated operand }; constexpr NoncopyableLiteralType w{42}; auto l4 = []{ return w.n_; }; // OK: w is not odr-used, capture is unnecessary // auto l5 = [=]{ return w.n_; }; // error: w needs to be captured by copy }
コピーでキャプチャされたエンティティをラムダ本体がodr-useする場合、クロージャ型のメンバにアクセスされます。エンティティをodr-useしていない場合、アクセスは元のオブジェクトに対して行われます。
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // not an odr-use: refers to g's const int N f(&N); // odr-use: causes N to be captured (by copy) // &N is the address of the closure object's member N, not g's N }(); }
参照によってキャプチャされた参照をラムダがodr-useする場合、それはキャプチャされた参照自体ではなく、元の参照が指すオブジェクトを使用しています。
#include <iostream> auto make_function(int& x) { return [&] { std::cout << x << '\n'; }; } int main() { int i = 3; auto f = make_function(i); // the use of x in f binds directly to i i = 5; f(); // OK: prints 5 }
キャプチャデフォルトが=のラムダの本体内では、キャプチャ可能なエンティティの型は、(ラムダがmutableでない場合、const修飾がよく追加されるため) キャプチャされているかのように扱われます。たとえそのエンティティが未評価のオペランドにあり、キャプチャされていない場合でも (例: decltype内)。
void f3() { float x, &r = x; [=] { // x and r are not captured (appearance in a decltype operand is not an odr-use) decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& because this lambda // is not mutable and x is an lvalue decltype(r) r1 = y1; // r1 has type float& (transformation not considered) decltype((r)) r2 = y2; // r2 has type float const& }; }
ラムダによって (暗黙的または明示的に) キャプチャされたすべてのエンティティは、ラムダ式によってodr-useされます (したがって、ネストされたラムダによる暗黙的なキャプチャは、囲むラムダでの暗黙的なキャプチャをトリガーします)。
暗黙的にキャプチャされたすべての変数は、ラムダの*到達スコープ*内で宣言されていなければなりません。
ラムダが囲むオブジェクト (thisまたは*thisとして) をキャプチャする場合、最も近い囲む関数は非静的メンバ関数でなければならないか、ラムダはデフォルトメンバ初期化子内になければなりません。
struct s2 { double ohseven = .007; auto f() // nearest enclosing function for the following two lambdas { return [this] // capture the enclosing s2 by reference { return [*this] // capture the enclosing s2 by copy (C++17) { return ohseven; // OK } }(); } auto g() { return [] // capture nothing { return [*this] {}; // error: *this not captured by outer lambda expression }(); } };
ラムダ式 (またはジェネリックラムダの関数呼び出し演算子の特殊化)(C++14 以降)が*thisまたは自動記憶域期間を持つ変数をODR-useする場合、それはラムダ式によってキャプチャされなければなりません。
void f1(int i) { int const N = 20; auto m1 = [=] { int const M = 30; auto m2 = [i] { int x[N][M]; // N and M are not odr-used // (ok that they are not captured) x[0][0] = i; // i is explicitly captured by m2 // and implicitly captured by m1 }; }; struct s1 // local class within f1() { int f; void work(int n) // non-static member function { int m = n * n; int j = 40; auto m3 = [this, m] { auto m4 = [&, j] // error: j is not captured by m3 { int x = n; // error: n is implicitly captured by m4 // but not captured by m3 x += m; // OK: m is implicitly captured by m4 // and explicitly captured by m3 x += i; // error: i is outside of the reaching scope // (which ends at work()) x += f; // OK: this is captured implicitly by m4 // and explicitly captured by m3 }; }; } }; }
クラスメンバは、初期化子なしのキャプチャによって明示的にキャプチャすることはできません (上記のように、capture-list では変数のみが許可されます)。
class S { int x = 0; void f() { int i = 0; // auto l1 = [i, x] { use(i, x); }; // error: x is not a variable auto l2 = [i, x = x] { use(i, x); }; // OK, copy capture i = 1; x = 1; l2(); // calls use(0,0) auto l3 = [i, &x = x] { use(i, x); }; // OK, reference capture i = 2; x = 2; l3(); // calls use(1,2) } };
ラムダが暗黙的なコピーによるキャプチャを使用してメンバをキャプチャする場合、そのメンバ変数のコピーは作成されません。メンバ変数mの使用は式(*this).mとして扱われ、*thisは常に参照によって暗黙的にキャプチャされます。
class S { int x = 0; void f() { int i = 0; auto l1 = [=] { use(i, x); }; // captures a copy of i and // a copy of the this pointer i = 1; x = 1; l1(); // calls use(0, 1), as if // i by copy and x by reference auto l2 = [i, this] { use(i, x); }; // same as above, made explicit i = 2; x = 2; l2(); // calls use(1, 2), as if // i by copy and x by reference auto l3 = [&] { use(i, x); }; // captures i by reference and // a copy of the this pointer i = 3; x = 2; l3(); // calls use(3, 2), as if // i and x are both by reference auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this, // including a copy of x i = 4; x = 4; l4(); // calls use(3, 2), as if // i and x are both by copy } };
ラムダ式がデフォルト引数内に出現する場合、明示的または暗黙的に何もキャプチャすることはできません。ただし、すべてのキャプチャがデフォルト引数内に出現する式の制約を満たす初期化子を持つ場合は除きます(C++14 以降)。
void f2() { int i = 1; void g1( int = [i] { return i; }() ); // error: captures something void g2( int = [i] { return 0; }() ); // error: captures something void g3( int = [=] { return i; }() ); // error: captures something void g4( int = [=] { return 0; }() ); // OK: capture-less void g5( int = [] { return sizeof i; }() ); // OK: capture-less // C++14 void g6( int = [x = 1] { return x; }() ); // OK: 1 can appear // in a default argument void g7( int = [x = i] { return x; }() ); // error: i cannot appear // in a default argument }
匿名共用体のメンバはキャプチャできません。ビットフィールドはコピーでのみキャプチャできます。
ネストされたラムダm2が、直近の囲むラムダm1によってもキャプチャされている何かをキャプチャする場合、m2のキャプチャは次のように変換されます。
- 囲むラムダ
m1がコピーによってキャプチャする場合、m2はm1のクロージャ型の非静的メンバをキャプチャし、元の変数または*thisではありません。m1がmutableでない場合、非静的データメンバはconst修飾されていると見なされます。 - 囲むラムダ
m1が参照によってキャプチャする場合、m2は元の変数または*thisをキャプチャします。
#include <iostream> int main() { int a = 1, b = 1, c = 1; auto m1 = [a, &b, &c]() mutable { auto m2 = [a, b, &c]() mutable { std::cout << a << b << c << '\n'; a = 4; b = 4; c = 4; }; a = 3; b = 3; c = 3; m2(); }; a = 2; b = 2; c = 2; m1(); // calls m2() and prints 123 std::cout << a << b << c << '\n'; // prints 234 }
|
ラムダが何かをキャプチャする場合、関数呼び出し演算子の明示的なオブジェクトパラメータ (もしあれば) の型は、次のいずれかである必要があります。
struct C { template<typename T> C(T); }; void func(int i) { int x = [=](this auto&&) { return i; }(); // OK int y = [=](this C) { return i; }(); // error int z = [](this C) { return 42; }(); // OK auto lambda = [n = 42] (this auto self) { return n; }; using Closure = decltype(lambda); struct D : private Closure { D(Closure l) : Closure(l) {} using Closure::operator(); friend Closure; }; D{lambda}(); // error } |
(C++23から) |
[編集] 注釈
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_lambdas |
200907L |
(C++11) | ラムダ式 |
__cpp_generic_lambdas |
201304L |
(C++14) | ジェネリックラムダ式 |
201707L |
(C++20) | ジェネリックラムダのための明示的なテンプレートパラメータリスト | |
__cpp_init_captures |
201304L |
(C++14) | ラムダ初期化キャプチャ |
201803L |
(C++20) | ラムダ初期化キャプチャでのパック展開を許可 | |
__cpp_capture_star_this |
201603L |
(C++17) | *thisの値キャプチャを[=, *this]として許可 |
__cpp_constexpr |
201603L |
(C++17) | constexprラムダ |
__cpp_static_call_operator |
202207L |
(C++23) | キャプチャなしラムダのための静的operator() |
暗黙的なラムダキャプチャのルールは、Defect Report P0588R1によってわずかに変更されました。2023年10月現在、いくつかの主要な実装ではまだDRが完全に実装されておらず、odr-useを検出する古いルールがまだ一部のケースで使用されています。
| P0588R1以前の古いルール | ||
|---|---|---|
|
capturesがcapture-defaultを持ち、囲むオブジェクト (
|
[編集] 例
この例では、ラムダをジェネリックアルゴリズムに渡す方法と、ラムダ式から生じるオブジェクトをstd::functionオブジェクトに格納する方法を示します。
#include <algorithm> #include <functional> #include <iostream> #include <vector> int main() { std::vector<int> c{1, 2, 3, 4, 5, 6, 7}; int x = 5; c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end()); std::cout << "c: "; std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; }); std::cout << '\n'; // the type of a closure cannot be named, but can be inferred with auto // since C++14, lambda could own default arguments auto func1 = [](int i = 6) { return i + 4; }; std::cout << "func1: " << func1() << '\n'; // like all callable objects, closures can be captured in std::function // (this may incur unnecessary overhead) std::function<int(int)> func2 = [](int i) { return i + 4; }; std::cout << "func2: " << func2(6) << '\n'; constexpr int fib_max {8}; std::cout << "Emulate `recursive lambda` calls:\nFibonacci numbers: "; auto nth_fibonacci = [](int n) { std::function<int(int, int, int)> fib = [&](int n, int a, int b) { return n ? fib(n - 1, a + b, a) : b; }; return fib(n, 0, 1); }; for (int i{1}; i <= fib_max; ++i) std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n"); std::cout << "Alternative approach to lambda recursion:\nFibonacci numbers: "; auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int { return n ? self(self, n - 1, a + b, a) : b; }; for (int i{1}; i <= fib_max; ++i) std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n"); #ifdef __cpp_explicit_this_parameter std::cout << "C++23 approach to lambda recursion:\n"; auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int { return n ? self(n - 1, a + b, a) : b; }; for (int i{1}; i <= fib_max; ++i) std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n"); #endif }
実行結果の例
c: 5 6 7 func1: 10 func2: 10 Emulate `recursive lambda` calls: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13 Alternative approach to lambda recursion: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
[編集] Defect reports
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 974 | C++11 | デフォルト引数はラムダ式のパラメータリストでは許可されていなかった ラムダ式のパラメータリスト |
許可 |
| CWG 1048 (N3638) |
C++11 | 戻り値の型は、1つのreturn文のみを含むラムダ本体に対してのみ推論できた ラムダ本体 |
戻り値の型推論を改善した 型推論 |
| CWG 1249 | C++11 | 囲む非mutableラムダのキャプチャされたメンバがconstと見なされるかどうか不明確であった と見なされるかどうか |
constと見なされる |
| CWG 1557 | C++11 | クロージャ型の変換関数の返される関数型の言語リンケージが指定されていなかった 変換関数 |
C++の言語リンケージを持つ 言語リンケージ |
| CWG 1607 | C++11 | ラムダ式が関数および関数テンプレートのシグネチャに出現できた 関数テンプレートのシグネチャ |
許可されなくなった。 |
| CWG 1612 | C++11 | 匿名共用体のメンバがキャプチャできた | 許可されなくなった。 |
| CWG 1722 | C++11 | キャプチャなしラムダの変換関数の例外指定が指定されていなかった 例外指定 |
変換関数はnoexceptである noexcept |
| CWG 1772 | C++11 | ラムダ本体内の__func__のセマンティクスが不明確であった | クロージャクラスのoperator()を参照する class's operator() |
| CWG 1780 | C++14 | ジェネリックラムダのクロージャ型のメンバが明示的にインスタンス化または特殊化できるか不明確であった インスタンス化または特殊化 |
どちらも許可されない |
| CWG 1891 | C++11 | クロージャは deleted デフォルトコンストラクタと暗黙的なコピー/ムーブコンストラクタを持っていた コピー/ムーブコンストラクタ |
デフォルトなし、デフォルト化されたコピー/ムーブコンストラクタ コピー/ムーブコンストラクタ |
| CWG 1937 | C++11 | 変換関数の結果を呼び出す効果については、どのオブジェクトに対してoperator()を呼び出すと 変換関数 同じ効果を持つか指定されていなかった |
クロージャ型のデフォルト構築されたインスタンスに対して インスタンス |
| CWG 1973 | C++11 | クロージャ型のoperator()のパラメータリストは、trailingで与えられたパラメータリストを参照できる trailing |
paramsのみを参照できる params |
| CWG 2011 | C++11 | 参照によってキャプチャされた参照について、キャプチャの識別子がどのエンティティを参照するかは指定されていなかった 参照 |
元の参照エンティティを参照する 参照エンティティ |
| CWG 2095 | C++11 | 関数へのrvalue参照をコピーでキャプチャする動作が不明確であった 不明確であった |
明確化された |
| CWG 2211 | C++11 | キャプチャがパラメータと同じ名前を持つ場合の動作は指定されていなかった パラメータ |
この場合、プログラムは 不正となる |
| CWG 2358 | C++14 | デフォルト引数に出現するラムダ式は、すべてのキャプチャがデフォルト引数に出現できる式で初期化されている場合でも、キャプチャなしでなければならなかった キャプチャなし デフォルト引数 |
そのようなラムダ式をキャプチャとともに許可する キャプチャ |
| CWG 2509 | C++17 | 各指定子は指定子シーケンス内に複数回出現できた 複数回出現 |
各指定子は指定子シーケンス内に最大1回しか出現できない 最大1回しか出現できない 指定子シーケンス |
| CWG 2561 | C++23 | 明示的なオブジェクトパラメータを持つラムダは、望ましくない関数ポインタ型への変換関数を持つことができた 変換関数 |
そのような変換関数を持たない 変換関数 |
| CWG 2881 | C++23 | 明示的なパラメータを持つoperator()は、継承がpublicではないか曖昧である場合に派生クラスに対してインスタンス化できた インスタンス化できた |
ill-formed にした |
| P0588R1 | C++11 | 暗黙的なラムダキャプチャのルールはodr-useを検出した | 検出が簡素化された |
[編集] 参照
auto 指定子 (C++11) |
式から推論される型を指定します |
| (C++11) |
コピー構築可能な任意の呼び出し可能オブジェクトをラップするコピー可能なラッパー (クラステンプレート) |
| (C++23) |
与えられた呼び出しシグネチャで修飾子をサポートする任意の呼び出し可能オブジェクトのムーブ専用ラッパー (クラステンプレート) |
[編集] 外部リンク
| ネストされた関数 - 別の (囲む) 関数内に定義された関数。 |