Expressions lambda en C++

Lambda Expressions C



Pourquoi l'expression lambda ?

Considérez l'énoncé suivant :

entiermonInt= 52;

Ici, myInt est un identifiant, une lvalue. 52 est un littéral, une prvalue. Aujourd'hui, il est possible de coder spécialement une fonction et de la mettre en position 52. Une telle fonction s'appelle une expression lambda. Considérez également le programme court suivant :







#comprendre

à l'aide de espace de nomsles heures;

entierfn(entierpar)

{

entierréponse=par+ 3;

revenirréponse;

}


entierprincipale()

{

fn(5);



revenir 0;

}

Aujourd'hui, il est possible de coder spécialement une fonction et de la mettre à la position de l'argument de 5, de l'appel de fonction, fn(5). Une telle fonction est appelée une expression lambda. L'expression lambda (fonction) dans cette position est une valeur pr.



Tout littéral, à l'exception du littéral de chaîne, est une valeur pr. L'expression lambda est une conception de fonction spéciale qui s'adapterait comme un littéral dans le code. C'est une fonction anonyme (sans nom). Cet article explique la nouvelle expression primaire C++, appelée expression lambda. Des connaissances de base en C++ sont indispensables pour comprendre cet article.



Contenu de l'article

Illustration de l'expression lambda

Dans le programme suivant, une fonction, qui est une expression lambda, est affectée à une variable :





#comprendre

à l'aide de espace de nomsles heures;

autofn= [](entierarrêter)

{

entierréponse=arrêter+ 3;

revenirréponse;

};


entierprincipale()

{

autovariable=fn(2);

cout <<variable<< ' ';


revenir 0;

}

La sortie est :

5

En dehors de la fonction main(), il y a la variable fn. Son type est automatique. Auto dans cette situation signifie que le type réel, tel que int ou float, est déterminé par l'opérande droit de l'opérateur d'affectation (=). À droite de l'opérateur d'affectation se trouve une expression lambda. Une expression lambda est une fonction sans le type de retour précédent. Notez l'utilisation et la position des crochets, []. La fonction renvoie 5, un entier, qui déterminera le type de fn.



Dans la fonction main(), il y a l'instruction :

autovariable=fn(2);

Cela signifie que fn en dehors de main(), finit par être l'identifiant d'une fonction. Ses paramètres implicites sont ceux de l'expression lambda. Le type de variab est auto.

Notez que l'expression lambda se termine par un point-virgule, tout comme la définition de classe ou de structure se termine par un point-virgule.

Dans le programme suivant, une fonction, qui est une expression lambda renvoyant la valeur 5, est un argument d'une autre fonction :

#comprendre

à l'aide de espace de nomsles heures;

annulerautrefn(entiernon1,entier (*ptr)(entier))

{

entiernon2= (*ptr)(2);

cout <<non1<< '' <<non2<< ' ';

}


entierprincipale()

{

autrefn(4,[](entierarrêter)

{

entierréponse=arrêter+ 3;

revenirréponse;

});


revenir 0;
}

La sortie est :

Quatre cinq

Il y a deux fonctions ici, l'expression lambda et la fonction otherfn(). L'expression lambda est le deuxième argument de otherfn(), appelé dans main(). Notez que la fonction lambda (expression) ne se termine pas par un point-virgule dans cet appel car, ici, il s'agit d'un argument (pas d'une fonction autonome).

Le paramètre de fonction lambda dans la définition de la fonction otherfn() est un pointeur vers une fonction. Le pointeur porte le nom ptr. Le nom, ptr, est utilisé dans la définition otherfn() pour appeler la fonction lambda.

La déclaration,

entiernon2= (*ptr)(2);

Dans la définition otherfn(), il appelle la fonction lambda avec un argument de 2. La valeur de retour de l'appel, '(*ptr)(2)' de la fonction lambda, est affectée à no2.

Le programme ci-dessus montre également comment la fonction lambda peut être utilisée dans le schéma de fonction de rappel C++.

Parties de l'expression lambda

Les parties d'une fonction lambda typique sont les suivantes :

[] () {}
  • [] est la clause de capture. Il peut contenir des objets.
  • () est pour la liste des paramètres.
  • {} est pour le corps de la fonction. Si la fonction est autonome, elle doit se terminer par un point-virgule.

Captures

La définition de la fonction lambda peut être affectée à une variable ou utilisée comme argument d'un autre appel de fonction. La définition d'un tel appel de fonction doit avoir comme paramètre, un pointeur vers une fonction, correspondant à la définition de la fonction lambda.

La définition de la fonction lambda est différente de la définition de la fonction normale. Il peut être affecté à une variable dans la portée globale ; cette fonction affectée à la variable peut également être codée à l'intérieur d'une autre fonction. Lorsqu'il est affecté à une variable de portée globale, son corps peut voir d'autres variables dans la portée globale. Lorsqu'il est affecté à une variable à l'intérieur d'une définition de fonction normale, son corps ne peut voir d'autres variables dans la portée de la fonction qu'avec l'aide de la clause de capture, [].

La clause de capture [], également connue sous le nom d'introducteur lambda, permet d'envoyer des variables depuis la portée environnante (fonction) dans le corps de la fonction de l'expression lambda. On dit que le corps de la fonction de l'expression lambda capture la variable lorsqu'il reçoit l'objet. Sans la clause capture [], une variable ne peut pas être envoyée de la portée environnante dans le corps de la fonction de l'expression lambda. Le programme suivant illustre cela, avec la portée de la fonction main(), comme portée environnante :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5;


autofn= [identifiant]()

{

cout <<identifiant<< ' ';

};

fn();


revenir 0;

}

La sortie est 5 . Sans le nom, id, à l'intérieur de [], l'expression lambda n'aurait pas vu l'identifiant de la variable de la portée de la fonction main().

Capture par référence

L'exemple ci-dessus d'utilisation de la clause capture est la capture par valeur (voir les détails ci-dessous). Lors de la capture par référence, l'emplacement (stockage) de la variable, par exemple l'identifiant ci-dessus, de la portée environnante, est rendu disponible à l'intérieur du corps de la fonction lambda. Ainsi, la modification de la valeur de la variable à l'intérieur du corps de la fonction lambda modifiera la valeur de cette même variable dans la portée environnante. Chaque variable répétée dans la clause de capture est précédée de l'esperluette (&) pour y parvenir. Le programme suivant illustre cela :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À';

autofn= [&identifiant,&pi,&ch]()

{

identifiant= 6;pi= 3.4;ch= 'B';

};

fn();

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ' ';

revenir 0;

}

La sortie est :

6, 3.4, B

Confirmer que les noms de variables à l'intérieur du corps de fonction de l'expression lambda correspondent aux mêmes variables en dehors de l'expression lambda.

Capture par valeur

Lors de la capture par valeur, une copie de l'emplacement de la variable, de la portée environnante, est disponible dans le corps de la fonction lambda. Bien que la variable à l'intérieur du corps de la fonction lambda soit une copie, sa valeur ne peut pas être modifiée à l'intérieur du corps pour le moment. Pour réaliser la capture par valeur, chaque variable répétée dans la clause capture n'est précédée de rien. Le programme suivant illustre cela :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À';

autofn= [id, ft, ch]()

{

//id = 6; pi = 3,4 ; ch = 'B';

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ' ';

};

fn();

identifiant= 6;pi= 3.4;ch= 'B';

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ' ';

revenir 0;

}

La sortie est :

5, 2.3, A

6, 3.4, B

Si l'indicateur de commentaire est supprimé, le programme ne sera pas compilé. Le compilateur émettra un message d'erreur indiquant que les variables à l'intérieur de la définition du corps de la fonction de l'expression lambda ne peuvent pas être modifiées. Bien que les variables ne puissent pas être modifiées à l'intérieur de la fonction lambda, elles peuvent être modifiées en dehors de la fonction lambda, comme le montre la sortie du programme ci-dessus.

Mélange de captures

La capture par référence et la capture par valeur peuvent être mélangées, comme le montre le programme suivant :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À'; bobobl= vrai;


autofn= [identifiant, pi,&ch,&bl]()

{

ch= 'B';bl= faux;

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


revenir 0;

}

La sortie est :

5, 2.3, B, 0

Lorsque tous capturés, sont par référence :

Si toutes les variables à capturer sont capturées par référence, alors un seul & suffira dans la clause de capture. Le programme suivant illustre cela :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À'; bobobl= vrai;


autofn= [&]()

{

identifiant= 6;pi= 3.4;ch= 'B';bl= faux;

};

fn();

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ',' <<bl<< ' ';


revenir 0;

}

La sortie est :

6, 3.4, B, 0

Si certaines variables doivent être capturées par référence et d'autres par valeur, alors un & représentera toutes les références, et les autres ne seront chacune précédées de rien, comme le montre le programme suivant :

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À'; bobobl= vrai;


autofn= [&, identifiant, pi]()

{

ch= 'B';bl= faux;

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


revenir 0;

}

La sortie est :

5, 2.3, B, 0

Notez que & seul (c'est-à-dire & non suivi d'un identifiant) doit être le premier caractère de la clause de capture.

Lorsque tous capturés, sont par valeur :

Si toutes les variables à capturer doivent être capturées par valeur, alors un seul = suffira dans la clause de capture. Le programme suivant illustre cela :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()
{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À'; bobobl= vrai;


autofn= [=]()

{

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


revenir 0;


}

La sortie est :

5, 2.3, A, 1

Noter : = est en lecture seule, à partir de maintenant.

Si certaines variables doivent être capturées par valeur et d'autres par référence, alors un = représentera toutes les variables copiées en lecture seule, et les autres auront chacune &, comme le montre le programme suivant :

#comprendre

à l'aide de espace de nomsles heures;

entierprincipale()

{

entieridentifiant= 5; flotterpi= 2.3; carboniserch= 'À'; bobobl= vrai;


autofn= [=,&ch,&bl]()

{

ch= 'B';bl= faux;

cout <<identifiant<< ',' <<pi<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


revenir 0;

}

La sortie est :

5, 2.3, B, 0

Notez que = seul doit être le premier caractère de la clause de capture.

Schéma de fonction de rappel classique avec expression Lambda

Le programme suivant montre comment un schéma de fonction de rappel classique peut être réalisé avec l'expression lambda :

#comprendre

à l'aide de espace de nomsles heures;

carboniser *sortir;


autocba= [](carboniserdehors[])

{

sortir=dehors;

};



annulerfonction principale(carbonisersaisir[],annuler (*pour)(carboniser[]))

{

(*pour)(saisir);

cout<<'pour fonction principale'<<' ';

}


annulerfn()

{

cout<<'Maintenant'<<' ';

}


entierprincipale()

{

carbonisersaisir[] = 'pour la fonction de rappel';

fonction principale(entrée, cba);

fn();

cout<<sortir<<' ';



revenir 0;

}

La sortie est :

pour fonction principale

Maintenant

pour la fonction de rappel

Rappelez-vous que lorsqu'une définition d'expression lambda est affectée à une variable dans la portée globale, son corps de fonction peut voir les variables globales sans utiliser la clause capture.

Le type à retour suiveur

Le type de retour d'une expression lambda est auto, ce qui signifie que le compilateur détermine le type de retour à partir de l'expression de retour (si présente). Si le programmeur veut vraiment indiquer le type de retour, alors il le fera comme dans le programme suivant :

#comprendre

à l'aide de espace de nomsles heures;

autofn= [](entierarrêter) -> entier

{

entierréponse=arrêter+ 3;

revenirréponse;

};


entierprincipale()

{

autovariable=fn(2);

cout <<variable<< ' ';


revenir 0;

}

La sortie est 5. Après la liste des paramètres, l'opérateur flèche est tapé. Ceci est suivi du type de retour (int dans ce cas).

Fermeture

Considérez le segment de code suivant :

structureCla

{

entieridentifiant= 5;

carboniserch= 'à';

}obj1, obj2;

Ici, Cla est le nom de la classe struct. Obj1 et obj2 sont deux objets qui seront instanciés à partir de la classe struct. L'expression lambda est similaire dans l'implémentation. La définition de la fonction lambda est une sorte de classe. Lorsque la fonction lambda est appelée (invoquée), un objet est instancié à partir de sa définition. Cet objet s'appelle une fermeture. C'est la fermeture qui fait le travail que le lambda est censé faire.

Cependant, le codage de l'expression lambda comme la structure ci-dessus aura obj1 et obj2 remplacés par les arguments des paramètres correspondants. Le programme suivant illustre cela :

#comprendre

à l'aide de espace de nomsles heures;

autofn= [](entierparam1,entierparam2)

{

entierréponse=param1+param2;

revenirréponse;

} (2,3);


entierprincipale()

{

auto=fn;

cout <<<< ' ';


revenir 0;

}

La sortie est 5. Les arguments sont 2 et 3 entre parenthèses. Notez que l'appel de fonction d'expression lambda, fn, ne prend aucun argument car les arguments ont déjà été codés à la fin de la définition de la fonction lambda.

Conclusion

L'expression lambda est une fonction anonyme. Il est en deux parties : classe et objet. Sa définition est une sorte de classe. Lorsque l'expression est appelée, un objet est formé à partir de la définition. Cet objet s'appelle une fermeture. C'est la fermeture qui fait le travail que le lambda est censé faire.

Pour que l'expression lambda reçoive une variable d'une portée de fonction externe, elle a besoin d'une clause de capture non vide dans son corps de fonction.