C++

Expressions lambda en C++

Expressions lambda en C++

Pourquoi l'expression lambda?

Considérez l'énoncé suivant :

    int monInt = 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 est appelée une expression lambda. Considérez également le programme court suivant :

#inclure
en utilisant l'espace de noms std ;
int fn(int par)

réponse int = par + 3 ;
retourner la réponse ;

int main()

nf(5) ;
renvoie 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
  • Parties de l'expression lambda
  • Captures
  • Schéma de fonction de rappel classique avec expression Lambda
  • Le type à retour suiveur
  • Fermeture
  • Conclusion

Illustration de l'expression lambda

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

#inclure
en utilisant l'espace de noms std ;
fn auto = [](int param)

réponse int = param + 3;
retourner la réponse ;
 ;
int main()

variable automatique = fn(2) ;
cout << variab << '\n';
renvoie 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 (=). A 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 int, qui déterminera le type de fn.

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

    variable automatique = fn(2) ;

Cela signifie que fn en dehors de main(), finit comme 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 :

#inclure
en utilisant l'espace de noms std ;
void otherfn (int no1, int (*ptr)(int))

int no2 = (*ptr)(2);
cout << no1 << " << no2 << '\n';

int main()

otherfn(4, [](int param)

réponse int = param + 3;
retourner la réponse ;
);
renvoie 0 ;

La sortie est :

    4  5

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, c'est un argument (pas 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,

    int no2 = (*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 avoir des articles.
  • () 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-assignée-à-variable peut aussi ê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 dans 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 de la portée (fonction) environnante dans le corps de la fonction de l'expression lambda. Le corps de la fonction de l'expression lambda est censé capturer 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5;
fn automatique = [id]()

cout << id << '\n';
 ;
fn();
renvoie 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 capture par valeur (voir les détails ci-dessous). Dans la capture par référence, l'emplacement (stockage) de la variable, e.g., id ci-dessus, de la portée environnante, est rendu disponible dans le corps de la fonction lambda. Ainsi, changer la valeur de la variable à l'intérieur du corps de la fonction lambda changera 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 atteindre cet objectif. Le programme suivant illustre cela :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A';
fn automatique = [&id, &ft, &ch]()

identifiant = 6 ; pi = 3.4 ; ch = 'B';
 ;
fn();
cout << id << ", " <<  ft << ", " <<  ch << '\n';
renvoie 0 ;

La sortie est :

    6, 3.4, B

Confirmation 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 rendue disponible à l'intérieur du 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A';
fn automatique = [id, ft, ch]()

//id = 6; pi = 3.4 ; ch = 'B';
cout << id << ", " <<  ft << ", " <<  ch << '\n';
 ;
fn();
identifiant = 6 ; pi = 3.4 ; ch = 'B';
cout << id << ", " <<  ft << ", " <<  ch << '\n';
renvoie 0 ;

La sortie est :

5, 2.3, un
6, 3.4, B

Si l'indicateur de commentaire est supprimé, le programme ne compilera pas. 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A'; bool bl = vrai;
fn automatique = [id, ft, &ch, &bl]()

ch = 'B'; bl = faux;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
 ;
fn();
renvoie 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A'; bool bl = vrai;
fn automatique = [&]()

identifiant = 6 ; pi = 3.4 ; ch = 'B'; bl = faux;
 ;
fn();
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
renvoie 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 :

en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A'; bool bl = vrai;
fn automatique = [&, id, ft]()

ch = 'B'; bl = faux;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
 ;
fn();
renvoie 0 ;

La sortie est :

5, 2.3, B, 0

Notez que & seul (je.e., & 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A'; bool bl = vrai;
fn automatique = [=]()

cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
 ;
fn();
renvoie 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 :

#inclure
en utilisant l'espace de noms std ;
int main()

int id = 5; pied flottant = 2.3 ; car ch = 'A'; bool bl = vrai;
fn automatique = [=, &ch, &bl]()

ch = 'B'; bl = faux;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
 ;
fn();
renvoie 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 :

#inclure
en utilisant l'espace de noms std ;
char *sortie;
auto cba = [](char out[])

sortie = sortie ;
 ;
void principalFunc(char input[], void (*pt)(char[]))

(*pt)(entrée);
cout<<"for principal function"<<'\n';

vide fn()

cout<<"Now"<<'\n';

int main()

char input[] = "pour la fonction de rappel" ;
principalFunc(entrée, cba);
fn();
cout<renvoie 0 ;

La sortie est :

pour fonction principale
À présent
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 de 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 :

#inclure
en utilisant l'espace de noms std ;
fn auto = [](int param) -> int

réponse int = param + 3;
retourner la réponse ;
 ;
int main()

variable automatique = fn(2) ;
cout << variab << '\n';
renvoie 0 ;

La sortie est de 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 :

struct Cla

int id = 5;
char ch = 'a';
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 :

#inclure
en utilisant l'espace de noms std ;
fn auto = [](int param1, int param2)

réponse int = param1 + param2;
retourner la réponse ;
(2, 3);
int main()

var auto = fn;
cout << var << '\n';
renvoie 0 ;

La sortie est de 5. Les arguments sont 2 et 3 entre parenthèses. Notez que l'appel de fonction d'expression lambda, fn, ne prend aucun argument puisque 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.

Comment inverser le sens de défilement de la souris et des pavés tactiles dans Windows 10
Souris et Pavé tactiles rendent non seulement l'informatique facile, mais plus efficace et moins chronophage. Nous ne pouvons pas imaginer une vie san...
Comment changer la taille, la couleur et le schéma du pointeur et du curseur de la souris sous Windows 10
Le pointeur et le curseur de la souris dans Windows 10 sont des aspects très importants du système d'exploitation. Cela peut également être dit pour d...
Moteurs de jeux gratuits et open source pour le développement de jeux Linux
Cet article couvrira une liste de moteurs de jeux gratuits et open source qui peuvent être utilisés pour développer des jeux 2D et 3D sur Linux. Il ex...