この質問はコードで完全に答えることはできません。多少「同等の」コードを記述できるかもしれませんが、標準はそのように指定されていません。
それが邪魔にならないので、に飛び込みましょう[expr.prim.lambda]
。最初に注意すべきことは、コンストラクタはでのみ言及されているということ[expr.prim.lambda.closure]/13
です。
関連した閉鎖型のラムダ式があればデフォルトコンストラクタを持っていないラムダ式があり、ラムダ・キャプチャーとそうでない不履行デフォルトコンストラクタを。デフォルトのコピーコンストラクターとデフォルトの移動コンストラクター([class.copy.ctor])があります。ラムダ式にラムダキャプチャがある場合はコピー割り当て演算子が削除され、それ以外の場合はデフォルトのコピーおよび移動割り当て演算子([class.copy.assign])が含まれます。[ 注:これらの特別なメンバー関数は通常どおり暗黙的に定義されるため、削除済みとして定義される場合があります。— エンドノート ]
つまり、すぐに、コンストラクターがオブジェクトのキャプチャーを定義する方法を正式に規定していないことは明らかです。かなり近づくことができますが(cppinsights.ioの回答を参照)、詳細は異なります(ケース4のその回答のコードがコンパイルされないことに注意してください)。
これらは、ケース1について説明するために必要な主な標準条項です。
[expr.prim.lambda.capture]/10
[...]
コピーによってキャプチャされたエンティティごとに、名前のない非静的データメンバーがクロージャタイプで宣言されます。これらのメンバーの宣言順序は指定されていません。このようなデータメンバーの型は、エンティティがオブジェクトへの参照である場合は参照型、エンティティが関数への参照である場合は参照される関数型への左辺値参照、そうでない場合は対応するキャプチャされたエンティティの型です。匿名組合のメンバーはコピーによって捕獲されないものとします。
[expr.prim.lambda.capture]/11
コピーによってキャプチャされたエンティティのodr使用であるラムダ式の複合ステートメント内のすべてのid式は、クロージャータイプの対応する名前のないデータメンバーへのアクセスに変換されます。[...]
[expr.prim.lambda.capture]/15
lambda-expressionが評価されると、コピーによってキャプチャされたエンティティを使用して、結果のクロージャーオブジェクトの対応する各非静的データメンバーが直接初期化され、init-capturesに対応する非静的データメンバーが次のように初期化されます。対応する初期化子によって示されます(コピーまたは直接初期化の場合があります)。[...]
これをケース1に適用してみましょう。
ケース1:値によるキャプチャ/デフォルトによる値によるキャプチャ
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
このラムダのクロージャタイプには、名前のない非静的データメンバー(と呼びましょう__x
)のタイプがありint
(x
参照でも関数でもないため)、x
ラムダ本体内へのアクセスはへのアクセスに変換され__x
ます。ラムダ式を評価するとき(つまり、に代入するときlambda
)は、で直接初期化 __x
しx
ます。
つまり、1つのコピーのみが行われます。クロージャー型のコンストラクターは関係しておらず、これを「通常の」C ++で表現することはできません(クロージャー型も集約型ではないことに注意してください)。
参照キャプチャには[expr.prim.lambda.capture]/12
次のものが含まれます。
エンティティは、暗黙的または明示的にキャプチャされているが、コピーによってキャプチャされていない場合、参照によってキャプチャされます。参照によってキャプチャされたエンティティのクロージャー型で、追加の無名の非静的データメンバーが宣言されるかどうかは指定されていません。[...]
参照の参照キャプチャに関する別の段落がありますが、それはどこでも行いません。
したがって、ケース2の場合:
ケース2:参照によるキャプチャ/デフォルトの参照によるキャプチャ
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
メンバーがクロージャタイプに追加されているかどうかはわかりません。x
ラムダ本体では、直接x
外部を参照するだけかもしれません。これはコンパイラの判断次第であり、C ++コードのソース変換ではなく、何らかの形の中間言語(コンパイラごとに異なる)でこれを行います。
Initキャプチャの詳細は、[expr.prim.lambda.capture]/6
次のとおりです。
init-captureはauto init-capture ;
、宣言領域がラムダ式の複合ステートメントである形式の変数を宣言して明示的にキャプチャするかのように動作しますが、次の点が異なります。
- (6.1)キャプチャがコピー(下記参照)の場合、キャプチャ用に宣言された非静的データメンバーと変数は、非静的データの存続期間を持つ同じオブジェクトを参照する2つの異なる方法として扱われます。メンバー、および追加のコピーと破棄は実行されません。
- (6.2)キャプチャが参照によるものである場合、クロージャーオブジェクトのライフタイムが終了すると、変数のライフタイムが終了します。
それでは、ケース3を見てみましょう。
ケース3:一般化されたinitキャプチャ
auto lambda = [x = 33]() { std::cout << x << std::endl; };
述べたように、これが変数によって作成されauto x = 33;
、明示的にコピーによってキャプチャされていると想像してください。この変数はラムダ本体内でのみ「可視」です。[expr.prim.lambda.capture]/15
前に述べたように、(__x
後世のための)クロージャー型の対応するメンバーの初期化は、ラムダ式の評価時に指定された初期化子によって行われます。
誤解を避けるために:これは、ここで2回初期化されることを意味するものではありません。これauto x = 33;
は、単純なキャプチャのセマンティクスを継承する「まるで」のようなものであり、説明されている初期化はそれらのセマンティクスの変更です。初期化は1回だけ行われます。
これには、ケース4も含まれます。
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
クロージャ型のメンバーは__p = std::move(unique_ptr_var)
、ラムダ式が評価されるとき(つまり、l
が割り当てられるとき)に初期化されます。アクセスp
ラムダ体では、へのアクセスに変換されます__p
。
TL; DR:最小限の数のコピー/初期化/移動のみが実行されます(期待/期待どおり)。私は、ラムダがされていることを前提となりません、正確に(他のシンタックスシュガーとは違って)ソース変換の観点から指定されたため、コンストラクタの観点で物事を表現することは余分の操作が必要となります。
これが質問で示された恐れを解決することを願っています:)