IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Fonctions anonymes (lambda)


Sujet :

Langage C++

  1. #1
    Membre actif
    Inscrit en
    Août 2009
    Messages
    52
    Détails du profil
    Informations forums :
    Inscription : Août 2009
    Messages : 52
    Par défaut Fonctions anonymes (lambda)
    Bonjour Tout le monde,

    Je voudrais savoir la différence entre une expression lambda et une fonction lambda.

    Quel est l'intérêt d'utiliser les fonctions lambda en C++?

    Pourquoi on les appelle anonymes?

    Si j'ai ce code qui est correct tel qu'il est écrit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
     
    #include <iostream>
     
    using namespace std;
     
    int main()
    {
       auto func=[] {cout << "hello" << endl; } ;
        func();
     
        auto f= [] () -> void {
     
           cout << "hello";
        };
        f();
     
         auto f1= [] () -> double {
     
            double r;
            cout << "donner un reel" << endl;
            cin >> r;
     
            return r*r;
        };
        cout << f1() << endl;
        return 0;
    }
    pourquoi il faut mettre le mot clé auto devant le nom des fonctions anonymes/lambda?
    par exemple pour la fonction f1: le type de retour est double. Pourquoi en remplaçant auto par double, le programme génère une erreur?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    double f1= [] () -> double {
     
            cout << "donner un reel" << endl;
            double r;
            cin >> r;
     
            return r*r;
        };
    Merci d'avance

  2. #2
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 748
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 748
    Par défaut
    Les fonctions anonymes/ lambda sont des fonctions qu'on a besoin juste pour 1 tâche spécifique : retour d'1 "thread", traitement à faire sur chaque élément d'1 collection, par exemple.
    C'est pour cela qu'elles sont anonymes, "sans nom" : on s'en fiche de cette fonction, on veut juste qu'elle fasse le travail.
    Et d'aileurs, elle est souvent passée directement en paramètre.

    Regarde la notion de fermeture : fermeture, lien wikipedia et sa version anglais + complète.

    Je ne connais pas bien cela en C++ , mais 1 fonction anonyme est 1 fonction.
    Donc le type doit être 1 type fonction. auto doit être là pour masquer 1 type "à la C++" complexe/ indigeste/ pas simple

  3. #3
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    750
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 750
    Par défaut
    On utilise auto pour déduire le type de la lambda parce que chaque lambda a un type différent qui correspond à son expression. Et comme ce type est unique et construit "sur place", le seul moyen de mettre la lambda dans une variable est d'utiliser auto pour déduire le type.

    par exemple pour la fonction f1: le type de retour est double. Pourquoi en remplaçant auto par double, le programme génère une erreur?
    Il y a confusion entre le type de retour d'un appel de la lambda et le type de la lambda.

  4. #4
    Membre actif
    Inscrit en
    Août 2009
    Messages
    52
    Détails du profil
    Informations forums :
    Inscription : Août 2009
    Messages : 52
    Par défaut
    Bonjour,

    Tout d'abord merci pour votre aide.
    J'ai une autre question:

    Est ce que la variable f1 contient la valeur retournée (r*r) par la fonction lambda ou bien elle contient un objet dont le type est déduit automatiquement (c'est pourquoi dans l'exemple on utilise auto et non pas double)?

    merci d'avance

  5. #5
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Citation Envoyé par nadia85 Voir le message
    Est ce que la variable f1 contient la valeur retournée (r*r) par la fonction lambda ou bien elle contient un objet dont le type est déduit automatiquement (c'est pourquoi dans l'exemple on utilise auto et non pas double)?
    Comme déjà écrit, f1 c'est la lambda et son type est unique; quant à la valeur retournée elle n'existera qu'au moment où on va utiliser la lambda.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     auto f1 = []( double x ) -> double { return x * x; };
     auto f2 = []( double x ) -> double { return x * x; };
     static_assert( ! std::is_same_v<decltype(f1),decltype(f2)> ); // f1 et f2 font exactement la même chose MAIS N'ONT PAS LE MEME TYPE
     
     double  y = f1( 42 );   // là, la lambda s'exécute et retourne un résultat.

  6. #6
    Membre actif
    Inscrit en
    Août 2009
    Messages
    52
    Détails du profil
    Informations forums :
    Inscription : Août 2009
    Messages : 52
    Par défaut
    Bonjour,

    Je vous remercie encore pour toutes ces explications.

    Pourquoi la lambda est anonyme alors qu'on lui a donné un nom (f1 par exemple?)?
    Est ce qu'on peut dire que les fonctions lambda sont des fonctions inline?

    Merci d'avance?

  7. #7
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Une lambda est une notation pour la création d'un objet.
    le type de cet objet n'est pas nommé (du moins, ce nom n'est pas accessible).
    Factuellement, l'objet en question est l'instance d'un type généré à la compilation, et qui contient un operator().

    L'objet en question peut être copié/déplacé/gardé dans une variable qui a un nom.

    Concretement, l'expression auto f = [factor, &total](int i) { return total += i * factor) est compilée comme si on avait écrit:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    auto f = (struct {
    decltype(factor) factor = "le scope englobant"::factor;
    decltype(total) & total = "le scope englobant"::total;
    auto operator() (int i) const { return total += i * factor; }
    } lambda, lambda);
    Note comme cette structure n'a pas de nom.

  8. #8
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Utiliser le terme fonction anonyme désigne doublement mal une lambda.
    Ça n'est pas une fonction, ça ressemble à une fonction. Et ça n'est pas toujours totalement anonyme, la lambda f1 à un nom, mais pas son type. Mais on peut aussi avoir ni type nommé, ni lambda nommée.

    Voilà 2 lambda qui n'ont pas de nom et dont le type n'est pas nommé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::cout << [](int x){return x*x;}(42) << std::endl;            // lambda est créée et immédiatement appelée pour la valeur 42
    std::vector  v{-1,-12,3,-5,0};
    std::sort( v.begin(), v.end(), [](auto x,auto y){return abs(x)<abs(y);} );  // la lambda "explique" que l'on trie par valeur absolue.

  9. #9
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 748
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 748
    Par défaut
    Citation Envoyé par nadia85 Voir le message
    Pourquoi la lambda est anonyme alors qu'on lui a donné un nom (f1 par exemple?)?
    Même si @dalfab et @ternel ont raison mais ce sont les détails d'implémentation du C++.

    Les fonctions anonymes sont 1 des bases de la programmation fonctionnelle (Lisp, OCam, Scheme, Erlang)
    Comme dans ces langages, tout est fonction, il faut pouvoir créer "à la volée" des fonctions pour faire 1 tâche spécifique. C'est le rôle d'1 fonction anonyme.
    Et cette fonction est soit passée en paramètre soit stockée dans 1 variable pour être appelée + tard.

    Et donc non , dans ton cas, cette fonction anonyme/ lambda n'a pas de nom : tu en crées 1 et tu la stockes dans la variable f1 (tu vois bien qu'il y a 1 affectation) (avec 1 type auto parce qu'en C++, les variables sont typées).


    Citation Envoyé par ternel Voir le message
    Factuellement, l'objet en question est l'instance d'un type généré à la compilation, et qui contient un operator().
    On appelle cet objet 1 foncteur ("functor" en anglais)

  10. #10
    Membre actif
    Inscrit en
    Août 2009
    Messages
    52
    Détails du profil
    Informations forums :
    Inscription : Août 2009
    Messages : 52
    Par défaut
    Bonsoir tout le monde,

    Comme la lambda est crée à la volée, cela veut dire qu'elle est une fonction inline?

    Merci d'avance

  11. #11
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 748
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 748
    Par défaut
    Citation Envoyé par nadia85 Voir le message
    Comme la lambda est crée à la volée, cela veut dire qu'elle est une fonction inline?
    La notion de inline (l'appel de la méthode est remplacé par le code *) n'existe plus depuis le C++ moderne.
    Cela a été remplacé pour 1 autre notion (1 même définition dans plusieurs unités de compilation, ou 1 truc comme cela)

    *, depuis c'est 1 détail du compilateur

    1 peu de lecture : inline specifier, lien cppreference.com en anglais
    The original intent of the inline keyword was to serve as an indicator to the optimizer that inline substitution of a function is preferred over function call, that is, instead of executing the function call CPU instruction to transfer control to the function body, a copy of the function body is executed without generating the call. This avoids overhead created by the function call (passing the arguments and retrieving the result) but it may result in a larger executable as the code for the function has to be repeated multiple times.

    Since this meaning of the keyword inline is non-binding, compilers are free to use inline substitution for any function that's not marked inline, and are free to generate function calls to any function marked inline. Those optimization choices do not change the rules regarding multiple definitions and shared statics listed above.

  12. #12
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Salut,
    Citation Envoyé par nadia85 Voir le message
    Bonjour,

    Je vous remercie encore pour toutes ces explications.

    Pourquoi la lambda est anonyme alors qu'on lui a donné un nom (f1 par exemple?)?
    Est ce qu'on peut dire que les fonctions lambda sont des fonctions inline?

    Merci d'avance?
    En fait, tu sembles, tout simplement, confondre la fonction elle-même et la variable qui correspond à cette fonction .

    Mais, rassures toi, cette confusion est "logique", au vu de l'usage qui en est fait, je vais donc essayer de t'expliquer plus clairement

    Dans la syntaxe du C++ (et de la plupart des langages, d'ailleurs), la déclaration d'une fonction, le fait de dire au compilateur (ou à l'interpréteur) qu'une fonction existe va nécessiter trois élément "obligatoires", qui sont:
    1. le nom de la fonction qui permettra d'y faire appel
    2. la liste des paramètres requis, qui permettra au compilateur (ou à l'interpréteur) de déterminer quelle "version" de la fonction utiliser, du moins si le langage autorise le fait d'avoir plusieurs fonctions portant le même nom (ce qui est le cas de presque tous les langages, à l'exception notoire du C);
    3. le type de retour de la fonction, pour que le compilateur (ou l'interpréteur) pour que l'on puisse récupérer, dans la fonction appelante, le résultat de la fonction qui a été appelée.

    En C++, cela prendra la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <type_de_retour>  <nom_de_la_fonction> (<liste_des_paramètres> )
    ou, si tu préfères un exemple plus concret, de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    double distance_euclidienne(Point2D const & a, Point2D const & b)
       |               |       |                                    |
       |              |        |___________________________________|
       |              |                                             |--->   liste_des_paramètres
       |              |----> nom_de_la_fonction
       |---> type_de_retour
    On se rend compte grâce à cela que le nom de la fonction est obligatoire si l'on veut créer une fonction "classique", qu'il fait partie intégrante de la syntaxe du langage,et que l'on ne peut pas l'oublier, au risque de se faire engueuler par le compilateur.

    la syntaxe pour définir une expression lambda sera quant à elle
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     [ <capture> ] ( <liste_des_paramètres> ) -> <type_de_retour_eventuel> {<ce_quil_faut_faire> };
    Et tu remarques qu'il n'y a absolument aucun nom, aucun moyen d'identifier cette fonction en vue de son "utilisation ultérieure".

    C'est pour cela que ce genre de fonction est appelé "fonction anonyme": parce qu'il s'agit effectivement du moyen dont on dispose pour définir un comportement bien particuler fournissant un résultat bien précis (ce qui est le but des fonctions, finalement ) sans que nous n'ayons besoin de trouver un nom qui nous permette de la désigner.

    Cependant, tu remarqueras que nous sommes bel et bien au niveau de la déclaration ou de la définition d'une fonction ici

    Par contre, lorsque tu écris un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    auto f1 = [ = ](int a) { /* .... */} ;
    ce que tu fais avec ce code, c'est définir une variable qui contient -- pour l'occasion -- l'adresse mémoire à laquelle commence le code binaire exécutable de la fonction anonyme que l'on définit "en même temps".

    Mais ce code agit finalement exactement de la même manière que si tu avais écrit un code proche de ou proche de Et le truc, c'est que l'on n'a pas d'autre choix que de fournir un nom pour les variables (ainsi que pour les constantes, d'ailleurs), car, autrement, nous n'aurions aucun moyen pour y accéder lorsqu'on en a besoin.

    Nous avons donc, dans le cas présent une variable (forcément) nommée qui permet d'accéder à un pointeur de fonction "pointant vers" ... une fonction anonyme.

    Avoue que ca fait classe non
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  13. #13
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 492
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 492
    Billets dans le blog
    1
    Par défaut
    Si jamais ça intéresse, j'ai écris un article assez complet sur comment fonctionnent les lambda expressions en C++ https://www.younup.fr/blog/demystifi...vec-cppinsight

    Ca montre pourquoi on utilise auto pour stocker ça, ce que ça génère derrière, pourquoi c'est callable, comment ça stocke les variables capturées.

  14. #14
    Membre actif
    Inscrit en
    Août 2009
    Messages
    52
    Détails du profil
    Informations forums :
    Inscription : Août 2009
    Messages : 52
    Par défaut
    Bonjour tout le monde,

    Le concept est maintenant clair grâce à vos explications très utiles.

    Merci

  15. #15
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    667
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 667
    Par défaut
    Salut,

    De mon côté, j'aurai quand même quelque chose à ajouter.

    Je n'ai jamais entendu parler d'une lambda comme étant un fonction anonyme mais plutôt (si ce n'est une closure/clôture/fermeture) une fonction objet. Certe elle n'a pas de nom, donc elle est anonyme, mais c'est avant tout une fonction objet, enfin c'est ce qu'elle devient. La vérité c'est que c'est juste une expression au même titre que l'expression conditionnelle, elle permet de construire du code, ici une fonction objet. Donc on peut dire que c'est simplement un sucre syntaxique pour construire des prédicats au plus près de l'endroit où on les utilise.

    Pour le inline, alors comme pour toute fonction membre définie dans le corps d'une classe, la fonction membre utile est effectivement "inlinée".

    Dans auto f= [] () {};, la lambda c'est juste l'expression à droite du signe égal. Le f ce n'est pas son nom, c'est une "référence" à la lambda. On peut dans certaines conditions demander un pointeur aussi double (*f) ()= [] () -> double {}; et le compilateur aux normes fera ce qu'il faut, un f() produira le résultat escompté.

    Voilà pour moi dans les grandes lignes.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [JS] Fonction anonyme et retour de variable ?
    Par lounislounis dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 25/07/2010, 00h23
  2. Fonctions anonymes et passage de variables
    Par Killua69 dans le forum Général JavaScript
    Réponses: 13
    Dernier message: 23/07/2010, 12h16
  3. fonction membre depuis fonction anonyme
    Par PoZZyX dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 09/06/2010, 17h30
  4. Fonction dans une fonction anonyme
    Par Shinjuku dans le forum Général JavaScript
    Réponses: 8
    Dernier message: 01/07/2009, 11h50
  5. fsolve & paramètres de la fonction anonyme
    Par Mathusalem dans le forum MATLAB
    Réponses: 1
    Dernier message: 05/07/2006, 10h04

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo