Lambda expressions (since C++11)
closure (スコープ内の変数を捕捉可能な無名関数オブジェクト)を構築します。
目次 |
構文
明示的なテンプレートパラメータリストを持たないラムダ式(非ジェネリックの可能性あり)
[
キャプチャ
]
前置属性
(オプション)
(
パラメータ
)
指定子
(オプション)
例外仕様
(オプション)
後置属性 (オプション) トレイリング戻り値型 (オプション) 要件 (オプション) 契約仕様 (オプション)
{
本体
}
|
(1) | ||||||||
[
キャプチャ
] {
本体
}
|
(2) | (C++23まで) | |||||||
[
キャプチャ
]
前置属性
(オプション)
トレイリング戻り値型
(オプション)
契約仕様
(オプション)
{
本体
}
|
(2) | (C++23以降) | |||||||
[
キャプチャ
]
前置属性
(オプション)
例外仕様
後置属性 (オプション) トレイリング戻り値型 (オプション) 契約仕様 (オプション)
{
本体
}
|
(3) | (C++23以降) | |||||||
[
キャプチャ
]
前置属性
(オプション)
指定子
例外仕様
(オプション)
後置属性 (オプション) トレイリング戻り値型 (オプション) 契約仕様 (オプション)
{
本体
}
|
(4) | (C++23以降) | |||||||
明示的なテンプレートパラメータリストを持つラムダ式 (常にジェネリック) (C++20以降)
[
キャプチャ
] <
テンプレートパラメータ
>
テンプレート制約
(オプション)
前方属性 (オプション)
(
パラメータ
)
指定子
(オプション)
例外仕様
(オプション)
後方属性 (オプション) トレイリング戻り値型 (オプション) 制約 (オプション) 契約仕様 (オプション)
{
本体
}
|
(1) | ||||||||
[
キャプチャ
] <
テンプレートパラメータ
>
テンプレート制約
(オプション)
{
本体
}
|
(2) | (C++23まで) | |||||||
[
キャプチャ
] <
テンプレートパラメータ
>
テンプレート制約
(オプション)
前方属性 (オプション) トレイリング戻り値型 (オプション) 契約仕様 (オプション)
{
本体
}
|
(2) | (C++23以降) | |||||||
[
キャプチャ
] <
テンプレートパラメータ
>
テンプレート制約
(オプション)
前方属性
(オプション)
例外仕様
後方属性 (オプション) トレイリング戻り値型 (オプション) 契約仕様 (オプション)
{
本体
}
|
(3) | (C++23以降) | |||||||
[
キャプチャ
] <
テンプレートパラメータ
>
テンプレート制約
(オプション)
前方属性
(オプション)
指定子
例外仕様
(オプション)
後方属性 (オプション) トレイリング戻り値型 (オプション) 契約仕様 (オプション)
{
本体
}
|
(4) | (C++23以降) | |||||||
説明
| captures | - | キャプチャするエンティティを 指定します 。 | ||||||||||||
| tparams | - |
空でないカンマ区切りの
テンプレートパラメータ
のリスト。ジェネリックラムダのテンプレートパラメータに名前を提供するために使用される(下記の
ClosureType::operator()
を参照)。
|
||||||||||||
| t-requires | - |
tparams
に
constraints
を追加します。
|
||||||||||||
| front-attr | - |
(C++23以降)
属性指定子シーケンスはクロージャ型の
operator
(
)
に適用され(したがって
[[
noreturn
]]
属性を使用可能)。
|
||||||||||||
| params | - |
クロージャ型の
parameter list
である
operator
(
)
のパラメータリスト。
|
||||||||||||
| specs | - |
以下の指定子のリスト。各シーケンスでは各指定子は最大1回のみ使用可能。
|
||||||||||||
| except | - | クロージャ型の operator ( ) に対して dynamic exception specification または (until C++20) noexcept specifier を提供します。 | ||||||||||||
| back-attr | - |
属性指定子シーケンスはクロージャ型の
operator
(
)
の型に適用されます(したがって
[[
noreturn
]]
属性は使用できません)。
|
||||||||||||
| trailing | - |
->
ret
、ここで
ret
は戻り値の型を指定します。
|
||||||||||||
| requires | - | (since C++20) クロージャ型の operator ( ) に 制約 を追加します。 | ||||||||||||
| contract-specs | - | (since C++26) クロージャ型の 関数契約指定子 の operator ( ) に対するリスト。 | ||||||||||||
| body | - | 関数本体。 |
|
|
(C++14以降) |
変数 __func__ は body の先頭で暗黙的に定義され、その意味は こちら で説明されている通りです。
クロージャ型
ラムダ式は、一意の無名非 union 非 aggregate クラス型のprvalue式であり、 クロージャ型 として知られています。この型は( ADL の目的で)ラムダ式を含む最小のブロックスコープ、クラススコープ、または名前空間スコープで宣言されます。
|
クロージャ型は、 構造的 型である。ただし、その条件は captures が空である場合に限る。 |
(C++20以降) |
クロージャ型は以下のメンバを持ち、これらは 明示的なインスタンス化 、 明示的な特殊化 、または (C++14以降) friend宣言 で名前を指定することはできません:
ClosureType:: operator()( params )
|
ret operator
(
)
(
params
)
{
body
}
|
(staticおよびconstが存在する可能性があります。下記を参照) | |
|
template
<
template
-
params
>
ret operator ( ) ( params ) { body } |
(C++14以降)
(ジェネリックラムダ、staticおよびconstが存在する可能性があります。下記を参照) |
|
ラムダ式の本体は、呼び出されたときに実行されます。変数にアクセスする際には、そのキャプチャされたコピー(コピーによってキャプチャされたエンティティの場合)、または元のオブジェクト(参照によってキャプチャされたエンティティの場合)にアクセスします。
operator ( ) のパラメータリストは、提供されている場合は params であり、それ以外の場合はパラメータリストは空です。
operator ( ) の戻り値の型は、 末尾 で指定された型です。
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 が使用された場合、 静的メンバ関数 です。 operator ( ) は、 params に明示的なオブジェクトパラメータが含まれている場合、 明示的オブジェクトメンバ関数 です。 |
(C++23以降) |
|
params 内の各パラメータについて、その型が auto で指定されている場合、発明されたテンプレートパラメータが template-params に追加されます(出現順)。対応する関数メンバが関数パラメータパックである場合、発明されたテンプレートパラメータは parameter pack となる可能性があります。 |
(C++14以降) |
|
ラムダ定義が明示的なテンプレートパラメータリストを使用する場合、そのテンプレートパラメータリストは operator ( ) で使用されます。 params の各パラメータで型が auto として指定されているものについては、追加の生成されたテンプレートパラメータがそのテンプレートパラメータリストの末尾に追加されます: |
(C++20以降) |
lambda式の例外指定 except は、 operator ( ) に適用されます。
名前探索 の目的、 this ポインタ の型と値の決定、および非静的クラスメンバへのアクセスにおいて、ラムダ式の operator ( ) の本体は、ラムダ式のコンテキストで考慮されます。
struct X { int x, y; int operator()(int); void f() { // 以下のラムダのコンテキストはメンバー関数 X::f です [=]() -> int { return operator()(this->x + y); // X::operator()(this->x + (*this).y) // this の型は 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から) | |
この ユーザー定義変換関数 は、ラムダ式が キャプチャ を持たず、 明示的なオブジェクトパラメータを持たない場合 (C++23以降) にのみ定義されます。これはクロージャオブジェクトのpublicな、 constexpr、 (C++17以降) non-virtual、non-explicit、const noexceptメンバー関数です。
|
この関数は、関数呼び出し演算子(またはジェネリックラムダの場合、その特殊化)が immediate function である場合、 immediate function となります。 |
(C++20以降) |
|
キャプチャなしのジェネリックラムダは、 operator ( ) と同じ発明されたテンプレートパラメータリストを持つユーザー定義変換関数テンプレートを持ちます。 |
(C++14以降) |
|
変換関数が返す値は、C++ language linkage を持つ関数へのポインタであり、これを呼び出すと、クロージャ型のデフォルト構築されたインスタンスに対してクロージャ型の関数呼び出し演算子を呼び出すのと同じ効果を持つ。 |
(C++14まで) |
|
変換関数(テンプレート)が返す値は、C++ language linkage を持つ関数へのポインタであり、これを呼び出すと以下のものと同じ効果を持つ:
|
(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以前) |
|
キャプチャ が指定されていない場合、クロージャ型はデフォルト化されたデフォルトコンストラクタを持ちます。それ以外の場合、デフォルトコンストラクタはありません(これには キャプチャデフォルト がある場合も含まれます。実際に何もキャプチャしていなくても)。 |
(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以降)
(それ以外の場合) |
|
|
コピー代入演算子は削除済みとして定義され(ムーブ代入演算子は宣言されない)、クロージャ型は CopyAssignable ではない。 |
(C++20まで) |
|
キャプチャ が指定されていない場合、クロージャ型はデフォルト化されたコピー代入演算子とデフォルト化されたムーブ代入演算子を持つ。それ以外の場合、削除済みのコピー代入演算子を持つ(これは キャプチャデフォルト が存在する場合も含み、実際に何もキャプチャしていなくても適用される)。 |
(C++20以降) |
ClosureType:: ~ClosureType()
|
~ClosureType
(
)
=
default
;
|
||
デストラクタは暗黙的に宣言されます。
ClosureType:: Captures
|
T1 a
;
T2 b
;
|
||
ラムダ式がコピーによって何かをキャプチャする場合(暗黙的にキャプチャ句
[=]
を使用するか、文字&を含まない明示的なキャプチャ、例えば
[a, b, c]
を使用する場合)、クロージャ型は、そのようにキャプチャされたすべてのエンティティのコピーを保持する、順序が未指定の無名の非静的データメンバを含みます。
初期化子なしのキャプチャに対応するデータメンバは、ラムダ式が評価されるときに 直接初期化 されます。初期化子付きのキャプチャに対応するデータメンバは、初期化子の要求に応じて初期化されます(コピー初期化または直接初期化の可能性があります)。配列がキャプチャされる場合、配列要素はインデックスの昇順で直接初期化されます。データメンバが初期化される順序は、それらが宣言される順序(未指定)です。
各データメンバの型は、対応するキャプチャされたエンティティの型です。ただし、エンティティが参照型を持つ場合を除きます(その場合、関数への参照は参照先の関数への左辺値参照としてキャプチャされ、オブジェクトへの参照は参照先のオブジェクトのコピーとしてキャプチャされます)。
参照によってキャプチャされるエンティティ(
capture-default
[&]
または文字&を使用する場合、例えば
[&a, &b, &c]
)については、クロージャ型に追加のデータメンバが宣言されるかどうかは未指定です
(ただし、そのような追加のメンバは
LiteralType
を満たさなければなりません
(C++17以降)
。
|
ラムダ式は以下の場所では許可されません: 未評価式 、 テンプレート引数 、 エイリアス宣言 、 typedef宣言 、および関数(または関数テンプレート)宣言内の関数本体と関数の デフォルト引数 を除くすべての場所。 |
(C++20まで) |
Lambdaキャプチャ
captures は、ラムダ関数本体の内部からアクセス可能な外部変数を定義します。その構文は以下のように定義されます:
| キャプチャデフォルト | (1) | ||||||||
| キャプチャリスト | (2) | ||||||||
キャプチャデフォルト
,
キャプチャリスト
|
(3) | ||||||||
| capture-default | - |
&
または
=
のいずれか
|
| capture-list | - | カンマ区切りの capture リスト |
capture
の構文は以下のように定義されます:
| 識別子 | (1) | ||||||||
識別子
...
|
(2) | ||||||||
| 識別子 初期化子 | (3) | (C++14以降) | |||||||
&
識別子
|
(4) | ||||||||
&
識別子
...
|
(5) | ||||||||
&
識別子
初期化子
|
(6) | (C++14以降) | |||||||
this
|
(7) | ||||||||
*
this
|
(8) | (C++17以降) | |||||||
...
識別子
初期化子
|
(9) | (C++20以降) | |||||||
&
...
識別子
初期化子
|
(10) | (C++20以降) | |||||||
capture-default
が
&
の場合、後続の単純キャプチャは
&
で始まってはなりません。
struct S2 { void f(int i); }; void S2::f(int i) { [&] {}; // OK: 参照によるデフォルトキャプチャ [&, i] {}; // OK: 参照によるキャプチャ、ただしiはコピーでキャプチャ [&, &i] {}; // エラー: 参照がデフォルトの場合の参照キャプチャ [&, this] {}; // OK、[&]と同等 [&, this, i] {}; // OK、[&, i]と同等 }
capture-default
が
=
の場合、後続の単純キャプチャは
&
で始まる必要があります
、または
*this
(C++17以降)
、または
this
(C++20以降)
。
struct S2 { void f(int i); }; void S2::f(int i) { [=] {}; // OK: デフォルトのコピーキャプチャ [=, &i] {}; // OK: コピーキャプチャ、ただしiは参照でキャプチャ [=, *this] {}; // C++17まで: エラー: 無効な構文 // C++17以降: OK: 外側のS2をコピーでキャプチャ [=, this] {}; // C++20まで: エラー: =がデフォルト時のthis // C++20以降: OK, [=]と同じ }
任意のキャプチャは一度だけ現れ、その名前は任意のパラメータ名と異なっていなければなりません:
struct S2 { void f(int i); }; void S2::f(int i) { [i, i] {}; // エラー: i が重複 [this, *this] {}; // エラー: "this" が重複 (C++17) [i] (int i) {}; // エラー: パラメータとキャプチャが同じ名前 }
ラムダ式は、変数をキャプチャしなくてもその変数を使用できます(その変数が
ラムダ式は、変数が以下の場合にキャプチャせずにその値を読み取ることができます
- const非volatileの整数型または列挙型を持ち、かつ 定数式 で初期化されている場合、または
- constexprであり、かつmutableメンバーを持たない場合。
現在のオブジェクト (
*
this
) は、キャプチャデフォルトが存在する場合、暗黙的にキャプチャすることができます。暗黙的にキャプチャされる場合、キャプチャデフォルトが
=
であっても、常に参照によってキャプチャされます。
キャプチャデフォルトが
=
の場合の
*
this
の暗黙的キャプチャは非推奨です。
(C++20以降)
以下のいずれかの条件を満たすラムダ式のみが、初期化子なしの capture-default または capture を持つことができます:
- その最も内側の 囲むスコープ は ブロックスコープ である。
- それが デフォルトメンバ初期化子 内に現れ、その最も内側の囲むスコープは対応する クラススコープ である。
| (C++26 以降) |
このようなラムダ式に対して、 reaching scope は、最も内側の外側の関数(とそのパラメータ)を含む、外側のスコープの集合として定義されます。これには、ネストされたブロックスコープと、このラムダがネストされている場合は外側のラムダのスコープも含まれます。
初期化子なしのキャプチャ(
this
キャプチャを除く)における
identifier
は、ラムダの
reaching scope
内で通常の
unqualified name lookup
を用いて検索されます。検索結果は、reaching scope で宣言された自動記憶域期間を持つ
variable
であるか、
対応する変数がこの要件を満たす
structured binding
(C++20以降)
でなければなりません。このエンティティは
explicitly captured
されます。
|
初期化子を持つキャプチャは、
init-capture
と呼ばれ、型指定子
これは、 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 な自動変数 、または対応する変数がアトミック記憶域期間を持つ structured binding (C++20以降) を明示的にキャプチャしていない場合、そのエンティティが式内の potentially-evaluated な式で使用されているとき(非静的クラスメンバの使用前に暗黙の 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: #1を呼び出し、xをキャプチャしない auto g0 = [](auto a) { f(x); }; // 同上 auto l1 = [=]{ f(x); }; // OK: xをキャプチャし(P0588R1以降)、#1を呼び出す // キャプチャは最適化により除去可能 auto g1 = [=](auto a) { f(x); }; // 同上 auto ltid = [=]{ typeid(x); }; // OK: xをキャプチャする(P0588R1以降) // xが未評価であっても // キャプチャは最適化により除去可能 auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2] = {}; f(x, selector); // OK: 依存式であるため、xをキャプチャする }; auto g3 = [=](auto a) { typeid(a + x); // a + xが未評価オペランドかどうかにかかわらず // xをキャプチャする }; constexpr NoncopyableLiteralType w{42}; auto l4 = []{ return w.n_; }; // OK: wはodr使用されず、キャプチャは不要 // auto l5 = [=]{ return w.n_; }; // エラー: wはコピーによるキャプチャが必要 }
ラムダ本体がコピーによってキャプチャされたエンティティを ODR-use する場合、クロージャ型のメンバがアクセスされます。エンティティをODR-useしていない場合、アクセスは元のオブジェクトに対して行われます:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // odr-useではない: gのconst int Nを参照 f(&N); // odr-use: Nのキャプチャ(コピーによる)を引き起こす // &NはクロージャオブジェクトのメンバNのアドレスであり、gの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); // f内のxの使用は直接iにバインドされる i = 5; f(); // OK: 5を出力 }
キャプチャデフォルト
=
を持つラムダ本体内部では、キャプチャ可能なエンティティの型は、それがキャプチャされたかのようになります(したがって、ラムダが
mutable
でない場合、しばしばconst修飾が追加されます)。これは、エンティティが未評価オペランド内にあり、実際にはキャプチャされていない場合(例えば
decltype
内)でも同様です:
void f3() { float x, &r = x; [=] { // xとrはキャプチャされない(decltypeオペランド内での出現はodr-useではない) decltype(x) y1; // y1はfloat型を持つ decltype((x)) y2 = y1; // y2はfloat const&型を持つ。なぜならこのラムダは // mutableではなく、xは左辺値であるため decltype(r) r1 = y1; // r1はfloat&型を持つ(変換は考慮されない) decltype((r)) r2 = y2; // r2はfloat const&型を持つ }; }
ラムダによって(暗黙的または明示的に)キャプチャされた任意のエンティティは、ラムダ式によってodr-usedされる(したがって、ネストされたラムダによる暗黙的キャプチャは、外側のラムダでの暗黙的キャプチャを引き起こす)。
暗黙的にキャプチャされるすべての変数は、ラムダの 到達可能スコープ 内で宣言されていなければなりません。
ラムダが外側のオブジェクトを( this または * this として)キャプチャする場合、最も近い外側の関数は非静的メンバ関数であるか、ラムダは デフォルトメンバ初期化子 内になければなりません:
struct s2 { double ohseven = .007; auto f() // 以下の2つのラムダ式に対する最も近い外側の関数 { return [this] // 外側のs2を参照でキャプチャ { return [*this] // 外側のs2をコピーでキャプチャ (C++17) { return ohseven; // OK } }(); } auto g() { return [] // 何もキャプチャしない { return [*this] {}; // エラー: *this が外側のラムダ式によってキャプチャされていない }(); } };
ラムダ式が (またはジェネリックラムダの関数呼び出し演算子の特殊化) (C++14以降) * this または自動記憶域期間を持つ任意の変数をODR使用する場合、そのラムダ式によって捕捉されなければならない。
void f1(int i) { int const N = 20; auto m1 = [=] { int const M = 30; auto m2 = [i] { int x[N][M]; // N と M は odr-used されていない // (キャプチャされていないことは問題ない) x[0][0] = i; // i は m2 によって明示的にキャプチャされ // m1 によって暗黙的にキャプチャされている }; }; struct s1 // f1() 内のローカルクラス { int f; void work(int n) // 非静的メンバ関数 { int m = n * n; int j = 40; auto m3 = [this, m] { auto m4 = [&, j] // エラー: j は m3 によってキャプチャされていない { int x = n; // エラー: n は m4 によって暗黙的にキャプチャされるが // m3 によってキャプチャされていない x += m; // OK: m は m4 によって暗黙的にキャプチャされ // m3 によって明示的にキャプチャされている x += i; // エラー: i は到達可能スコープの外側にある // (スコープは work() で終了する) x += f; // OK: this は m4 によって暗黙的にキャプチャされ // m3 によって明示的にキャプチャされている }; }; } }; }
クラスメンバは初期化子なしのキャプチャで明示的にキャプチャできません(上述のように、 capture-list で許可されているのは variables のみです):
class S { int x = 0; void f() { int i = 0; // auto l1 = [i, x] { use(i, x); }; // エラー: xは変数ではありません auto l2 = [i, x = x] { use(i, x); }; // OK、コピーキャプチャ i = 1; x = 1; l2(); // use(0,0)を呼び出します auto l3 = [i, &x = x] { use(i, x); }; // OK、参照キャプチャ i = 2; x = 2; l3(); // use(1,2)を呼び出します } };
ラムダがメンバーを暗黙的なコピーキャプチャでキャプチャする場合、そのメンバー変数のコピーは作成されません:メンバー変数
m
の使用は式
(
*
this
)
.
m
として扱われ、
*
this
は常に暗黙的に参照によってキャプチャされます:
class S { int x = 0; void f() { int i = 0; auto l1 = [=] { use(i, x); }; // iのコピーとthisポインタのコピーをキャプチャ // thisポインタのコピーをキャプチャ i = 1; x = 1; l1(); // use(0, 1)を呼び出す。iはコピー、 // xは参照によるキャプチャのように振る舞う auto l2 = [i, this] { use(i, x); }; // 上記と同じ、明示的に記述 i = 2; x = 2; l2(); // use(1, 2)を呼び出す。iはコピー、 // xは参照によるキャプチャのように振る舞う auto l3 = [&] { use(i, x); }; // iを参照でキャプチャし、 // thisポインタのコピーをキャプチャ i = 3; x = 2; l3(); // use(3, 2)を呼び出す。iとxの両方が // 参照によるキャプチャのように振る舞う auto l4 = [i, *this] { use(i, x); }; // *thisのコピーを作成、 // xのコピーを含む i = 4; x = 4; l4(); // use(3, 2)を呼び出す。iとxの両方が // コピーによるキャプチャのように振る舞う } };
ラムダ式が デフォルト引数 内に現れる場合、明示的または暗黙的に何もキャプチャすることはできません (すべてのキャプチャがデフォルト引数内に現れる式の制約を満たす初期化子を持つ場合を除く) (C++14以降) :
void f2() { int i = 1; void g1( int = [i] { return i; }() ); // エラー: 何かをキャプチャしている void g2( int = [i] { return 0; }() ); // エラー: 何かをキャプチャしている void g3( int = [=] { return i; }() ); // エラー: 何かをキャプチャしている void g4( int = [=] { return 0; }() ); // OK: キャプチャなし void g5( int = [] { return sizeof i; }() ); // OK: キャプチャなし // C++14 void g6( int = [x = 1] { return x; }() ); // OK: 1はデフォルト引数内に // 現れることができる void g7( int = [x = i] { return x; }() ); // エラー: iはデフォルト引数内に // 現れることができない }
匿名共用体 のメンバはキャプチャできません。 ビットフィールド はコピーによるキャプチャのみ可能です。
ネストされたラムダ
m2
が、直近の外側のラムダ
m1
によってもキャプチャされている何かをキャプチャする場合、
m2
のキャプチャは以下のように変換されます:
-
外側のラムダ
m1がコピーでキャプチャする場合、m2は元の変数や * this ではなく、m1のクロージャ型の非静的メンバをキャプチャする。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(); // m2()を呼び出し、123を出力 std::cout << a << b << c << '\n'; // 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) | キャプチャなしラムダのstatic operator ( ) |
暗黙的なラムダキャプチャのルールは、欠陥報告 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'; // クロージャの型は名前を付けることができないが、autoで推論可能 // C++14以降、ラムダはデフォルト引数を持つことが可能 auto func1 = [](int i = 6) { return i + 4; }; std::cout << "func1: " << func1() << '\n'; // すべての呼び出し可能オブジェクトと同様に、クロージャは std::function でキャプチャ可能 // (これは不要なオーバーヘッドを招く可能性があります) std::function<int(int)> func2 = [](int i) { return i + 4; }; std::cout << "func2: " << func2(6) << '\n'; constexpr int fib_max {8}; std::cout << "再帰的ラムダ呼び出しをエミュレート:\nフィボナッチ数: "; 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 << "ラムダ再帰の代替アプローチ:\nフィボナッチ数: "; 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のラムダ再帰へのアプローチ:\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 `再帰的ラムダ`呼び出しのエミュレート: フィボナッチ数列: 0, 1, 1, 2, 3, 5, 8, 13 ラムダ再帰への代替アプローチ: フィボナッチ数列: 0, 1, 1, 2, 3, 5, 8, 13
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 974 | C++11 |
ラムダ式のパラメータリストでデフォルト引数が
許可されていなかった |
許可される |
|
CWG 1048
( N3638 ) |
C++11 |
戻り値型の推定はラムダ本体に単一の
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 |
| CWG 1772 | C++11 | ラムダ本体における __func__ の意味が明確でなかった |
クロージャクラスの
operator()を参照する |
| CWG 1780 | C++14 |
ジェネリックラムダのクロージャ型のメンバーを明示的に
インスタンス化または明示的特殊化できるかが不明確だった |
どちらも許可されない |
| CWG 1891 | C++11 |
クロージャは削除されたデフォルトコンストラクタと
暗黙のコピー/ムーブコンストラクタを持っていた |
デフォルトコンストラクタなし、
デフォルト化されたコピー/ムーブコンストラクタ |
| CWG 1937 | C++11 |
変換関数の結果を呼び出す効果について、
どのオブジェクトでその operator ( ) を呼び出すかが 規定されていなかった |
クロージャ型のデフォルト構築された
インスタンス上で |
| CWG 1973 | C++11 |
クロージャ型の
operator
(
)
のパラメータリストが
末尾 で与えられたパラメータリストを参照できた |
params
のみを
参照可能 |
| CWG 2011 | C++11 |
参照によって捕捉された参照について、
捕捉の識別子がどのエンティティを参照するかが規定されていなかった |
元々参照されていた
エンティティを参照する |
| CWG 2095 | C++11 |
関数への右辺値参照をコピーで捕捉する
動作が明確でなかった |
明確化された |
| CWG 2211 | C++11 |
捕捉とパラメータが同じ名前を持つ場合の
動作が規定されていなかった |
この場合プログラムは
不適格となる |
| CWG 2358 | C++14 |
デフォルト引数に現れるラムダ式は、
すべての捕捉がデフォルト引数に現れることができる 式で初期化されている場合でもキャプチャなしでなければならなかった |
そのような捕捉を持つ
ラムダ式を許可する |
| CWG 2509 | C++17 |
各指定子は指定子シーケンス内に複数回
現れることができた |
各指定子は指定子シーケンス内に
最大1回のみ現れることができる |
| CWG 2561 | C++23 |
明示的なオブジェクトパラメータを持つラムダが
望ましくない関数ポインタ型への変換関数を持つ可能性があった |
そのような変換関数を
持たない |
| CWG 2881 | C++23 |
明示的なパラメータを持つ
operator
(
)
が、継承がpublicでないか
曖昧な場合に派生クラスに対してインスタンス化される可能性があった |
不適格となる |
| P0588R1 | C++11 | 暗黙のラムダ捕捉のルールがodr-useを検出していた | 検出が簡素化された |
関連項目
auto
指定子
(C++11)
|
式から推論される型を指定する |
|
(C++11)
|
任意のコピー構築可能な呼び出し可能オブジェクトのコピー可能ラッパー
(クラステンプレート) |
|
(C++23)
|
特定の呼び出しシグネチャで修飾子をサポートする任意の呼び出し可能オブジェクトのムーブ専用ラッパー
(クラステンプレート) |
外部リンク
| ネストされた関数 - 別の( 外側の )関数内で定義された関数。 |