Programmation • IoT

Chapitre 8 – Les expressions lambda

Document réservé

Vous consultez actuellement la présentation publique de ce chapitre.

Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.

Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).

8 Les expressions lambda 8.1 Pourquoi les expressions lambda ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2 Une solution avec une fonction classique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3 Notre première expression lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.8 Le rôle des crochets : les captures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.11 Modifier une variable extérieure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.12 Capturer plusieurs variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.13 Captures générales avec [=] et [&] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.14 Capture par valeur et mutable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.18 Trier selon notre propre critère . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.20 Transformer des éléments avec std::transform . . . . . . . . . . . . . . . . . . . . . . . . . 8.22 Lambda ou fonction classique ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.23 Lambda et programmation moderne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2026 – C++ Partie III Les expressions lambda
Accueil
Les expressions lambda Pourquoi les expressions lambda ? Dans le chapitre précédent, nous avons rencontré des algorithmes capables d’effectuer des opérations sur les éléments d’un conteneur et certain de ces algorithmes savent directement ce qu’ils doivent rechercher, on peut prendre comme exemple std::vector<int> valeurs{10, 20, 30, 40}; auto it = std::find( Ici, l’algorithme recherche simplement la valeur 30, mais supposons maintenant que nous voulions rechercher : • le premier nombre supérieur à 25 ; • le premier nombre négatif ; • le premier nombre pair ; • le premier objet satisfaisant une condition particulière. L’algorithme ne peut pas deviner la condition que nous voulons appliquer mais on peut lui fournir un comportement à adopter Une solution avec une fonction classique Supposons que nous voulions rechercher le premier entier supérieur à 25. Nous pouvons commencer par écrire une fonction : bool superieurA25(int x) std::vector<int> valeurs{10, 20, 30, 40}; auto it = std::find_if( 2026 – C++ Partie III Les expressions lambda L’algorithme std::find_if applique successivement la fonction superieurA25() aux éléments du conteneur, conceptuellement cela donne : La recherche s’arrête donc sur la valeur 30, qui correspond, mais la fonction n’est peut-être utilisée qu’une seule fois et uniquement à cet endroit du programme, mais pour rentabiliser l’opération, le C++ moderne permet d’écrire cette petite fonction directement là où elle est nécessaire. Notre première expression lambda La fonction vue ci-dessus, bool superieurA25(int x) peut être remplacée par Il s’agit d’une expression lambda qui permet écrire directement : auto it = std::find_if( La lambda représente ici le comportement que std::find_if doit appliquer à chaque élément. Anatomie d’une lambda On peut distinguer trois parties principales :
Accueil
2026 – C++ Partie III Les expressions lambda La forme générale est donc : [captures](parametres) Une lambda est un objet Une expression lambda ne représente pas simplement une syntaxe abrégée pour déclarer une fonction ordinaire. Lorsqu’une lambda est rencontrée, le compilateur crée un type particulier, appelé type de fermeture (closure type), par exemple : auto estPair = [](int x) Puis appeler cet objet comme une fonction : std::cout << estPair(8) << ’\n’; std::cout << estPair(9) << ’\n’; Nous obtenons respectivement : L’objet estPair est donc appelable (callable) et son type exact est généré par le compilateur et c’est pourquoi auto est particulièrement naturel dans ce cas • Une expression lambda crée un objet appelable d’un type généré par le compilateur. • Une lambda peut donc être stockée dans une variable et appelée avec la syntaxe habituelle : Paramètres d’une lambda Les paramètres fonctionnent de manière très similaire à ceux d’une fonction ordinaire, nous trouvons des lambdas sans paramètre : std::cout << « Bonjour\n »;
Accueil
2026 – C++ Partie III Les expressions lambda des lambdas avec un paramètre : et des lambdas avec plusieurs paramètres : [](double a, double b) Nous pouvons par exemple écrire : auto addition = [](double a, double b) std::cout << addition(3.5, 2.0) << ’\n’; Dans de nombreux cas, le compilateur peut déduire le type retourné, ainsi : auto carre = [](double x) retourne naturellement un double mais nous pouvons également préciser explicitement le type : auto carre = [](double x) -> double est appelée type de retour final (trailing return type). Cette syntaxe n’est pas propre aux lambdas : elle peut également être utilisée avec des fonctions ordinaires mais dans les cas simples, la déduction automatique est généralement préférable. Le rôle des crochets : les captures La partie la plus particulière d’une lambda est probablement Il s’agit de la liste de captures et jusqu’à présent, elle était vide. Considérons maintenant :
Accueil
2026 – C++ Partie III auto test = [](int x) Les expressions lambda Cette écriture ne fonctionne pas car la variable locale limite est extérieure à la lambda. Pour que la lambda puisse utiliser cette variable locale, nous devons indiquer comment elle doit être capturée. Nous pouvons capturer limite par valeur auto test = [limite](int x) La lambda reçoit alors sa propre représentation de la valeur de limite au moment où elle est créée, par auto test = [limite](int x) std::cout << test(30) << ’\n’; La lambda continue ici à utiliser la valeur capturée lors de sa création, c.-à-d. et test(30) retourne donc true. • Une capture par valeur réalise une capture de l’état de la variable au moment de la création de l’objet lambda. • Une modification ultérieure de la variable extérieure ne modifie pas la valeur capturée. Capture par référence Nous pouvons au contraire capturer la variable par référence, auto test = [&limite](int x)
Accueil
2026 – C++ Partie III Les expressions lambda Cette fois, la lambda utilise la variable extérieure elle-même. auto test = [&limite](int x) std::cout << test(30) << ’\n’; retourne maintenant false car la lambda voit en effet la nouvelle valeur 100. Modifier une variable extérieure Une capture par référence permet également de modifier la variable extérieure, par exemple : auto incrementer = [&compteur]() std::cout << compteur << ’\n’; Le programme affiche : La lambda travaille directement sur la variable compteur existante. Capturer plusieurs variables Plusieurs variables peuvent être indiquées dans les crochets : auto appartient = [minimum, maximum](int x) return x >= minimum && x <= maximum; Il est également possible de mélanger capture par valeur et capture par référence : auto test = [limite, &compteur](int x)
Accueil
2026 – C++ Partie III Les expressions lambda • limite est capturée par valeur ; • compteur est capturé par référence. Captures générales avec [=] et [&] Le C++ fournit deux syntaxes permettant de définir un mode de capture par défaut, avec les variables locales utilisées par la lambda sont capturées par valeur, par exemple auto test = [=](int x) return x >= minimum && x <= maximum; les variables locales utilisées sont capturées par référence, exemple : Ces écritures sont pratiques, mais une capture explicite peut rendre plus visible ce dont dépend réellement une lambda, par exemple : indique immédiatement au lecteur quelles variables extérieures sont utilisées. Capture par valeur et mutable Par défaut, une variable capturée par valeur ne peut pas être modifiée par le corps d’une lambda ordinaire, auto f = [compteur]()
Accueil
2026 – C++ Partie III Les expressions lambda permet de modifier l’état interne de l’objet lambda, en notant auto f = [compteur]() mutable std::cout << compteur << ’\n’; Nous pouvons appeler : Mais la variable extérieure : La lambda modifie sa propre copie capturée, et non la variable originale. Capture et durée de vie La distinction entre capture par valeur et capture par référence possède également une conséquence importante sur la durée de vie. Une capture par valeur appartient à l’objet lambda et une capture par référence désigne au contraire un objet extérieur. Il faut donc s’assurer que cet objet existe toujours lorsque la lambda Considérons conceptuellement une lambda conservée après la disparition de la variable qu’elle avait capturée par référence, la référence contenue dans la lambda ne désignerait alors plus un objet valide. Nous retrouvons exactement un problème déjà rencontré avec les pointeurs et les références : Une référence n’assure pas la durée de vie de l’objet qu’elle désigne. Cette question devient particulièrement importante lorsqu’une lambda est stockée pour être exécutée plus std::find_if et les lambdas Nous pouvons maintenant revenir à l’algorithme et rechercher le premier entier supérieur à une limite choisie avec le bout de code :
Accueil
2026 – C++ Partie III Les expressions lambda std::vector<int> valeurs{10, 20, 30, 40, 50}; auto it = std::find_if( Si un élément satisfait la condition, it le désigne. Nous pouvons donc écrire : if (it != valeurs.end()) std::cout << *it << ’\n’; Le programme affiche la valeur L’algorithme fournit le mécanisme de recherche et la lambda fournit le critère de recherche. std::for_each et les lambdas applique une opération à chaque élément d’un intervalle, par exemple : std::vector<int> valeurs{10, 20, 30, 40}; std::cout << x << ’\n’; La lambda est appelée successivement avec chaque élément et nous pourrions également modifier les éléments si nous le désirons
Accueil
2026 – C++ Partie III Les expressions lambda Le conteneur contient alors : Dans un cas aussi simple, une boucle basée sur un intervalle serait souvent plus lisible : for (int& x : valeurs) Le fait qu’un algorithme existe ne signifie donc pas qu’il doit toujours être utilisé et comme souvent, la lisibilité du code reste un critère essentiel. Trier selon notre propre critère std::sort(v.begin(), v.end()); trie les valeurs dans l’ordre croissant lorsque leur opérateur < possède cette signification, mais nous pouvons fournir notre propre critère pour trier des entiers dans l’ordre décroissant par exemple : La lambda reçoit ici deux éléments et doit indiquer si le premier doit être placé avant le second. Nous pouvons ainsi définir des ordres beaucoup plus intéressants. Considérons la classe Mesure, et la déclaration : std::vector<Mesure> mesures; Nous pouvons trier les objets selon leur valeur : [](const Mesure& a, const Mesure& b) return a.valeur < b.valeur;
Accueil
2026 – C++ Partie III Les expressions lambda [](const Mesure& a, const Mesure& b) return a.temps < b.temps; Le même conteneur peut donc être trié selon différents critères sans modifier la classe Mesure. C’est une utilisation particulièrement importante des lambdas. Transformer des éléments avec std::transform L’algorithme std::tranform permet d’appliquer une transformation à une séquence. std::vector<double> valeurs{1.0, 2.0, 3.0, 4.0}; std::vector<double> carres(valeurs.size()); Nous pouvons écrire : Le conteneur carres contient alors : Nous pouvons lire cette instruction comme : Pour chaque élément compris entre valeurs.begin() et valeurs.end(), appliquer la transformation définie par la lambda et placer le résultat à partir de carres.begin(). Les lambdas génériques Depuis C++14, les paramètres d’une lambda peuvent eux-mêmes utiliser auto, par exemple : auto addition = [](auto a, auto b) Nous pouvons alors écrire :
Accueil
2026 – C++ Partie III Les expressions lambda Le compilateur génère les comportements nécessaires selon les types utilisés, une telle lambda est appelée Nous retrouvons ici l’esprit de la programmation générique et des templates, mais avec une syntaxe particulièrement compacte. Lambda ou fonction classique ? Les lambdas ne remplacent pas les fonctions ordinaires et une fonction classique reste généralement préférable lorsqu’une opération : • possède une signification propre dans le programme ; • est utilisée à plusieurs endroits ; • devient suffisamment longue ou complexe ; • mérite un nom explicite. Une lambda est particulièrement adaptée lorsqu’un petit comportement est nécessaire localement, par [](const Mesure& a, const Mesure& b) return a.valeur < b.valeur; Le critère de tri est visible exactement à l’endroit où il est utilisé et il n’est pas nécessaire de rechercher ailleurs dans le programme une fonction auxiliaire pour comprendre le comportement de l’algorithme. Lambda et programmation moderne Les expressions lambda sont devenues un élément important du C++ moderne et sont notamment utilisées • les algorithmes de la bibliothèque standard ; • les traitements de conteneurs ; • la programmation événementielle ; • l’exécution différée ; • certaines formes de programmation parallèle. Elles permettent surtout de considérer un comportement comme une valeur que l’on peut créer, stocker et
Accueil
2026 – C++ Partie III Les expressions lambda Une expression lambda permet de définir localement un objet appelable. Sa forme générale est : [captures](parametres) Les paramètres fonctionnent de manière analogue à ceux d’une fonction ordinaire. Le type de retour peut généralement être déduit automatiquement ou être indiqué explicitement : [](double x) -> double La liste de captures permet d’utiliser des variables locales extérieures à la lambda, une capture par valeur : conserve une valeur dans l’objet lambda. Une capture par référence : permet d’accéder à la variable extérieure elle-même. définissent respectivement une capture par défaut par valeur ou par référence. permet de modifier l’état des valeurs capturées par valeur dans l’objet lambda et enfin, les lambdas s’associent naturellement aux algorithmes tels que : L’algorithme définit alors le mécanisme général tandis que la lambda définit le comportement particulier à
Accueil
P4-Chap-08 Les expressions lambda
Accueil
2026 – C++ Partie III Les expressions lambda
Accueil
Termes à ajouter au glossaire
Contenu

Inscription

×
Cancel