Lambda Expressions en C ++

Lambda Expressions en C ++

Pourquoi l'expression de lambda?

Considérez la déclaration suivante:

int myInt = 52;

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

#inclure
Utilisation de Namespace Std;
int fn (int par)

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

int main()

fn (5);
retour 0;

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

Tout littéral sauf que le littéral des cordes est un prvalue. L'expression de lambda est une conception de fonction spéciale qui s'adapterait comme un code littéral dans le code. C'est une fonction anonyme (sans nom). Cet article explique la nouvelle expression primaire C ++, appelée l'expression de lambda. Les connaissances de base en C ++ sont une exigence pour comprendre cet article.

Contenu de l'article

  • Illustration de l'expression de lambda
  • Parties de l'expression de Lambda
  • Captures
  • Schéma de fonction de rappel classique avec expression de lambda
  • Le type de retour en arrière
  • Fermeture
  • Conclusion

Illustration de l'expression de lambda

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

#inclure
Utilisation de Namespace Std;
auto fn = [] (int param)

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

Auto variab = fn (2);
couter << variab << '\n';
retour 0;

La sortie est:

5

En dehors de la fonction principale (), 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 le bon opérande de l'opérateur d'attribution (=). À droite de l'opérateur d'affectation se trouve une expression de lambda. Une expression de 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:

Auto variab = fn (2);

Cela signifie, fn extérieur main (), finit comme l'identifiant d'une fonction. Ses paramètres implicites sont ceux de l'expression de lambda. Le type pour Variab est auto.

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

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

#inclure
Utilisation de Namespace Std;
void OtherFn (int no1, int (* ptr) (int))

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

int main()

autresfn (4, [] (intam)

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

La sortie est:

4 5

Il y a deux fonctions ici, l'expression lambda et la fonction autre. L'expression de lambda est le deuxième argument de l'autre (), 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 la fonction lambda dans la définition de la fonction OUTREFN () est un pointeur vers une fonction. Le pointeur a le nom, ptr. Le nom, ptr, est utilisé dans la définition autre.

La déclaration,

int no2 = (* ptr) (2);

Dans la définition autrefn (), il appelle la fonction lambda avec un argument de 2. La valeur de retour de l'appel, "(* ptr) (2)" de la fonction lambda, est attribuée au 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 de 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 seule, alors elle devrait se terminer par un point-virgule.

Captures

La définition de la fonction lambda peut être attribuée à une variable ou utilisée comme argument à un appel de fonction différent. La définition d'un tel appel de fonction devrait 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 attribué à une variable dans la portée globale; Cette fonction assistée à la variable peut également être codée dans 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 peut voir d'autres variables dans la portée de la fonction uniquement avec l'aide de la clause Capture, [], [].

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

#inclure
Utilisation de Namespace Std;
int main()

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

couter << id << '\n';
;
fn ();
retour 0;

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

Capture par référence

L'exemple ci-dessus l'utilisation de la clause de capture est capturée par valeur (voir les détails ci-dessous). En capturant par référence, l'emplacement (stockage) de la variable, e.g., Id ci-dessus, de la portée environnante, est 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 par l'ampes et / &) pour y parvenir. Le programme suivant illustre ceci:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a';
auto fn = [& id, & ft, & ch] ()

id = 6; ft = 3.4; ch = 'b';
;
fn ();
couter << id << ", " << ft << ", " << ch << '\n';
retour 0;

La sortie est:

6, 3.4, b

Confirmant que les noms de variables à l'intérieur du corps de fonction de l'expression de lambda sont destinés aux mêmes variables en dehors de l'expression de lambda.

Capturation par valeur

En capture par valeur, une copie de l'emplacement de la variable, de la portée environnante, est 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 à partir de maintenant. Pour atteindre la capture par valeur, chaque variable répétée dans la clause de capture n'est précédée de rien. Le programme suivant illustre ceci:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a';
auto fn = [id, ft, ch] ()

// id = 6; ft = 3.4; ch = 'b';
couter << id << ", " << ft << ", " << ch << '\n';
;
fn ();
id = 6; ft = 3.4; ch = 'b';
couter << id << ", " << ft << ", " << ch << '\n';
retour 0;

La sortie est:

5, 2.3, un
6, 3.4, b

Si l'indicateur de commentaire est supprimé, le programme ne compile pas. Le compilateur publiera un message d'erreur selon lequel les variables à l'intérieur de la définition du corps de fonction de l'expression de 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élanger les captures

La capture par référence et la capture par valeur peut être mitigée, comme le montre le programme suivant:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a'; bool bl = true;
auto fn = [id, ft et ch, & bl] ()

ch = 'b'; bl = false;
couter << id << ", " <<
ft << ", " << ch <<
"," << bl << '\n';
;
fn ();
retour 0;

La sortie est:

5, 2.3, b, 0

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

Si toutes les variables à capturer sont capturées par référence, alors une seule et suffira dans la clause de capture. Le programme suivant illustre ceci:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a'; bool bl = true;
auto fn = [&] ()

id = 6; ft = 3.4; ch = 'b'; bl = false;
;
fn ();
couter << id << ", " <<
ft << ", " << ch <<
"," << bl << '\n';
retour 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, une et représentera toutes les références, et le reste ne sera chacun pas précédé de rien, comme le montre le programme suivant:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a'; bool bl = true;
auto fn = [&, id, ft] ()

ch = 'b'; bl = false;
couter << id << ", " <<
ft << ", " << ch <<
"," << bl << '\n';
;
fn ();
retour 0;

La sortie est:

5, 2.3, b, 0

Notez que et seul (je.e., & non suivi d'un identifiant) doit être le premier caractère de la clause de capture.

Quand tous capturés, sont par valeur:

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

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a'; bool bl = true;
auto fn = [=] ()

couter << id << ", " <<
ft << ", " << ch <<
"," << bl << '\n';
;
fn ();
retour 0;

La sortie est:

5, 2.3, a, 1

Note: = 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 le reste aura chacun et, comme le montre le programme suivant:

#inclure
Utilisation de Namespace Std;
int main()

int id = 5; float ft = 2.3; char ch = 'a'; bool bl = true;
auto fn = [=, & ch, & bl] ()

ch = 'b'; bl = false;
couter << id << ", " << ft <<
"," << ch << ", " <<
blandir << '\n';
;
fn ();
retour 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 de lambda

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

#inclure
Utilisation de Namespace Std;
sortie de char *;
Auto CBA = [] (Char Out [])

sortie = out;
;
void PrincipalFunc (char char [], void (* pt) (char []))

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

void fn ()

couter<<"Now"<<'\n';

int main()

char char [] = "pour la fonction de rappel";
PrincipalFunc (entrée, CBA);
fn ();
couter<retour 0;

La sortie est:

Pour la fonction principale
Maintenant
pour la fonction de rappel

Rappelons que lorsqu'une définition d'expression de lambda est attribuée à une variable dans la portée globale, son corps de fonction peut voir des variables globales sans utiliser la clause de capture.

Le type de retour en arrière

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

#inclure
Utilisation de Namespace Std;
auto fn = [] (int param) -> int

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

Auto variab = fn (2);
couter << variab << '\n';
retour 0;

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

Fermeture

Considérez le segment de code suivant:

struct cl

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 de la classe struct. LAMBDA L'expression est similaire dans la mise en œuvre. 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é de sa définition. Cet objet est appelé une fermeture. C'est la fermeture qui fait le travail que le lambda devrait faire.

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

#inclure
Utilisation de Namespace Std;
auto fn = [] (int param1, int param2)

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

auto var = fn;
couter << var << '\n';
retour 0;

La sortie est 5. Les arguments sont 2 et 3 entre parenthèses. Notez que l'appel de la 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 de lambda est une fonction anonyme. C'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 est appelé une fermeture. C'est la fermeture qui fait le travail que le lambda devrait faire. Pour que l'expression de Lambda reçoive une variable d'une portée de la fonction extérieure, il a besoin d'une clause de capture non vide dans son corps de fonction.