Cette question ne peut pas être entièrement répondu dans le code. Vous pourrez peut-être écrire du code quelque peu "équivalent", mais la norme n'est pas spécifiée de cette façon.
Avec cela à l'écart, plongons-nous [expr.prim.lambda]
. La première chose à noter est que les constructeurs ne sont mentionnés que dans [expr.prim.lambda.closure]/13
:
Le type de fermeture associé à une expression lambda n'a pas de constructeur par défaut si l' expression lambda a une capture lambda et un constructeur par défaut par défaut sinon. Il a un constructeur de copie par défaut et un constructeur de déplacement par défaut ([class.copy.ctor]). Il a un opérateur d'affectation de copie supprimé si l' expression lambda a une capture lambda et des opérateurs d'affectation de copie et de déplacement par défaut dans le cas contraire ([class.copy.assign]). [ Remarque: Ces fonctions membres spéciales sont implicitement définies comme d'habitude et peuvent donc être définies comme supprimées. - note de fin ]
Donc, dès le départ, il devrait être clair que les constructeurs ne sont pas formellement comment la capture d'objets est définie. Vous pouvez être assez proche (voir la réponse cppinsights.io), mais les détails diffèrent (notez que le code de cette réponse pour le cas 4 ne se compile pas).
Ce sont les principales clauses standard nécessaires pour discuter du cas 1:
[expr.prim.lambda.capture]/10
[...]
Pour chaque entité capturée par copie, un membre de données non statique non nommé est déclaré dans le type de fermeture. L'ordre de déclaration de ces membres n'est pas précisé. Le type d'un tel membre de données est le type référencé si l'entité est une référence à un objet, une référence lvalue au type de fonction référencé si l'entité est une référence à une fonction, ou le type de l'entité capturée correspondante dans le cas contraire. Un membre d'un syndicat anonyme ne doit pas être capturé par copie.
[expr.prim.lambda.capture]/11
Chaque expression id dans l'instruction composée d'une expression lambda qui est une utilisation odr d'une entité capturée par copie est transformée en un accès au membre de données sans nom correspondant du type de fermeture. [...]
[expr.prim.lambda.capture]/15
Lorsque l'expression lambda est évaluée, les entités capturées par copie sont utilisées pour initialiser directement chaque membre de données non statique correspondant de l'objet de fermeture résultant, et les membres de données non statiques correspondant aux captures init sont initialisés comme indiqué par l'initialiseur correspondant (qui peut être une copie ou une initialisation directe). [...]
Appliquons ceci à votre cas 1:
Cas 1: capture par valeur / capture par défaut par valeur
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Le type de fermeture de ce lambda aura un membre de données non statique non nommé (appelons-le __x
) de type int
(car il x
ne s'agit ni d'une référence ni d'une fonction), et les accès à l' x
intérieur du corps lambda sont transformés en accès à __x
. Lorsque nous évaluons l'expression lambda (c'est-à-dire lors de l'affectation à lambda
), nous initialisons directement __x
avec x
.
Bref, une seule copie a lieu . Le constructeur du type de fermeture n'est pas impliqué et il n'est pas possible de l'exprimer en C ++ "normal" (notez que le type de fermeture n'est pas non plus un type agrégé ).
La capture de référence implique [expr.prim.lambda.capture]/12
:
Une entité est capturée par référence si elle est implicitement ou explicitement capturée mais pas capturée par copie. Il n'est pas spécifié si des membres de données non statiques supplémentaires non nommés sont déclarés dans le type de fermeture pour les entités capturées par référence. [...]
Il y a un autre paragraphe sur la capture de références, mais nous ne faisons cela nulle part.
Donc, pour le cas 2:
Cas 2: capture par référence / capture par défaut par référence
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Nous ne savons pas si un membre est ajouté au type de fermeture. x
dans le corps lambda pourrait se référer directement à l' x
extérieur. C'est au compilateur de comprendre, et il le fera dans une certaine forme de langage intermédiaire (qui diffère d'un compilateur à un autre), pas une transformation source du code C ++.
Les captures init sont détaillées dans [expr.prim.lambda.capture]/6
:
Une capture init se comporte comme si elle déclarait et capturait explicitement une variable de la forme auto init-capture ;
dont la région déclarative est l'instruction composée de l'expression lambda, sauf que:
- (6.1) si la capture est par copie (voir ci-dessous), le membre de données non statique déclaré pour la capture et la variable sont traités comme deux façons différentes de se référer au même objet, qui a la durée de vie des données non statiques membre, et aucune copie ni destruction supplémentaire n'est effectuée, et
- (6.2) si la capture est par référence, la durée de vie de la variable se termine lorsque la durée de vie de l'objet de fermeture se termine.
Compte tenu de cela, regardons le cas 3:
Cas 3: capture d'initialisation généralisée
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Comme indiqué, imaginez cela comme une variable créée par auto x = 33;
et explicitement capturée par copie. Cette variable n'est "visible" que dans le corps lambda. Comme indiqué [expr.prim.lambda.capture]/15
précédemment, l'initialisation du membre correspondant du type de fermeture ( __x
pour la postérité) se fait par l'initialiseur donné lors de l'évaluation de l'expression lambda.
Pour éviter tout doute: cela ne signifie pas que les choses sont initialisées deux fois ici. Le auto x = 33;
est un "comme si" pour hériter de la sémantique des captures simples, et l'initialisation décrite est une modification de cette sémantique. Une seule initialisation se produit.
Cela couvre également le cas 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Le membre de type de fermeture est initialisé par __p = std::move(unique_ptr_var)
lorsque l'expression lambda est évaluée (c'est-à-dire lorsqu'elle l
est affectée à). Les accès à p
dans le corps lambda sont transformés en accès à __p
.
TL; DR: Seul le nombre minimal de copies / initialisations / déplacements est effectué (comme on pourrait l'espérer / s'y attendre). Je suppose que les lambdas ne sont pas spécifiés en termes de transformation source (contrairement à d'autres sucres syntaxiques) exactement parce que l' expression des choses en termes de constructeurs nécessiterait des opérations superflues.
J'espère que cela apaise les craintes exprimées dans la question :)