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

C++ Discussion :

auto ou const auto pour les lambdas ?


Sujet :

C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 486
    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 486
    Points : 13 696
    Points
    13 696
    Billets dans le blog
    1
    Par défaut auto ou const auto pour les lambdas ?
    Bonjour

    Je suis de la team "const autant que possible". Donc, dès que je vois une déclaration de variable à laquelle je peux ajouter const, je le fais.

    Je fais une exception à cette règle : quand je stocke une lambda expression, je n'ajoute jamais const. Je fais auto foo = []() {}; et non const auto foo = []() {};.

    Pourtant, en suivant la logique "const autant que possible", je devrais aussi utiliser const dans cette situation.

    Comment faites-vous ? auto ou const auto pour les lambdas ? Pourquoi ?

    PS : pour celles et ceux qui ne savent pas, on ne peut changer la valeur de la variable :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main() {
        auto f = []() {};
        f = []() {};
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    <source>:3:7: error: no viable overloaded '='
        3 |     f = []() {};
          |     ~ ^ ~~~~~~~
    <source>:2:14: note: candidate function (the implicit copy assignment operator) not viable: no known conversion from '(lambda at <source>:3:9)' to 'const (lambda at <source>:2:14)' for 1st argument
        2 |     auto f = []() {};
          |              ^
    1 error generated.
    Compiler returned: 1
    EDIT : on peut produire un exemple où le typage correspond et où l'affectation est quand même rejetée (de toute façon, c'est pas un cas très utile) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int main() {
        auto f = []() {};
        auto g = f;
        f = g;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    <source>:4:6: error: object of type '(lambda at <source>:2:14)' cannot be assigned because its copy assignment operator is implicitly deleted
        4 |         f = g;
          |           ^
    <source>:2:14: note: lambda expression begins here
        2 |     auto f = []() {};
          |              ^

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    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 196
    Points : 17 165
    Points
    17 165
    Par défaut
    A priori je suis d'accord qu'il faudrait un const.

    Cela dit, j'ai une réserve sur ton exemple.
    Chaque lambda est d'un type unique. Du coup, dans ton exemple, tu essaies d'affecter deux types non compatibles.

    Par contre, j'ai passé dix minutes à trouver un contre exemple, et j'arrive quand même à un refus.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <iostream>
    int main() {
        int state = 0;
        auto f = [state]() mutable { return ++state; };
        auto backup = f;
        auto g = f;
        for (int i = 0; i < 10; ++i) std::cout << f() << ' ';
        for (int i = 0; i < 10; ++i) std::cout << g() << ' ';
        f = backup;
        return 0;
    }
    Avec GCC, en C++17, ce code-ci échoue avec le message suivant, mais uniquement à la ligne f = backup;.
    error: use of deleted function ‘main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)’

  3. #3
    Membre émérite Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 047
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 047
    Points : 2 254
    Points
    2 254
    Par défaut
    Hello,
    Cela fait longtemps que je ne suis pas venu ici !
    Ça n'a pas trop de sens d'avoir un lambda const car par defaut l'operator()est const.

    Une lambda ce n'est rien de plus que du sucre syntaxique d'un foncteur, donc si f est const il ne peux modifier son état dans l'operator(), donc ne peut modifier ces variables internes comme toutes méthodes const d'une classe, or l'operator() étant const par défaut, this est const par défaut dans l'operator().

    Cela n'apporte rien.

  4. #4
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 486
    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 486
    Points : 13 696
    Points
    13 696
    Billets dans le blog
    1
    Par défaut
    Mon seul bon argument pour ne pas ajouter const est effectivement que ça ne sert à rien, la variable étant en quelque sorte "implicitement const". L'autre argument que je peux avoir c'est "ça met plus en évidence la lambda", mais il me semble un peu léger. J'ai l'impression que personne n'a de bons arguments pour ne pas mettre const

    Les raisons techniques du langage derrière ne sont pas vraiment intéressantes dans un document de "code style", et ce n'est d'ailleurs pas le sujet ici

  5. #5
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    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 196
    Points : 17 165
    Points
    17 165
    Par défaut
    Citation Envoyé par Bktero Voir le message
    J'ai l'impression que personne n'a de bons arguments pour ne pas mettre const
    Ce qui est en soit une bonne raison pour le mettre.

    Je préfère être explicite, et je cite les C++ Core Guidelines:
    I.1: Make interfaces explicit
    ES.25: Declare an object const or constexpr unless you want to modify its value later on

  6. #6
    Membre chevronné Avatar de gabriel21
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    548
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2007
    Messages : 548
    Points : 1 989
    Points
    1 989
    Par défaut
    Pourquoi alourdir le code si de toutes les façons nous ne pouvons pas modifier un lambda ?

    Est-il utile d'expliciter un état qui ne peut être intrinsèquement autre ?
    La règle du const tient parce qu'une variable peut être modifiée ou pas. Cela permet d'expliciter la volonté du programmeur voir du modeleur dans les cas où plusieurs options sont possible. J'ai tendance à mettre le const dans les modèle UML.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 632
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 711
    Points
    30 711
    Par défaut
    Salut,
    Citation Envoyé par ternel Voir le message
    Ce qui est en soit une bonne raison pour le mettre.

    Je préfère être explicite, et je cite les C++ Core Guidelines:
    I.1: Make interfaces explicit
    ES.25: Declare an object const or constexpr unless you want to modify its value later on
    Et tu as tout à fait raison avec ce point. Il faut juste se mettre d'accord sur les termes que l'on utilise...

    Car la ligne directrice ES25 parle d'objets, et non d'expressions. C'est donc qu'il est utile de faire la distinction entre les expressions qui représentent effectivement objets -- comme les données et les constantes -- et les expressions qui ne représentent pas des objets, comme les fonctions.

    Or, les expressions lambda sont plus proche des fonctions que des objets, ce qui fait qu'il n'y a finalement que peut de raison de les déclarer constantes.

    Tu pourras, bien sur, me dire que les fonction peuvent renvoyer une valeur constante, et que l'on peut même trouver facilement une situation dans laquelle c'est effectivement la fonction qui est déclarée constante (lorsque l'on a affaire à une fonction membre).

    Dans le premier cas, cela ne change rien, vu que toutes les fonctions ne renvoient pas forcément une donnée, et que la constance de la donnée renvoyée n'a -- en définitive -- absolument rien à voir avec le comportement de la fonction en elle-même.

    Dans le deuxième cas (les fonctions membres constantes), la constance n'a pour but que d'indiquer le fait que la donnée à partir de laquel la fonction sera appelée ne sera pas modifiée. Ce qui nous place bel et bien sur une constance d'objet...

    Lorsque tu assignes une expression lambda à f (ou à g, ou à tout ce que tu peux vouloir) sous la forme de f =[](){}, tu crées d'avantage un alias sur une expression (f étant au final une expression) qu'un objet (une donnée dont le type serait l'expression elle-même), le mot clé auto étant là pour représenter le retour de l'expression dans le cas où l'expression serait destinée à renvoyer une valeur.

    Au final, nous pourrions dire que "oui, il faut rendre tous les objets que l'on peut constants, à moins d'avoir de bonnes raisons de croire qu'ils devront être modifiés", dans le respect de la ES25; cependant, il faut se rendre compte que seules les données sont susceptibles d'être considérées comme des objets

  8. #8
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    397
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 397
    Points : 698
    Points
    698
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Or, les expressions lambda sont plus proche des fonctions que des objets, ce qui fait qu'il n'y a finalement que peut de raison de les déclarer constantes.
    Mouais. C'est quand même tiré par les cheveux de faire une telle distinction. cppreference parle même de "unnamed function object" pour désigner une lambda. A mon sens, l'argument de ternel est complètement vailde.

    Citation Envoyé par Astraya Voir le message
    Une lambda ce n'est rien de plus que du sucre syntaxique d'un foncteur, donc si f est const il ne peux modifier son état dans l'operator(), donc ne peut modifier ces variables internes comme toutes méthodes const d'une classe, or l'operator() étant const par défaut, this est const par défaut dans l'operator().
    Pour "modifier ces variables internes" (c'est à dire les captures), il faut de toute façon explicitement mettre "mutable".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
     
    int main() {
        int i = 123;
        const auto f = [i]() {
            std::cout << i << std::endl; // ok, on modifie pas la capture
        };
        f();
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
     
    int main() {
        int i = 123;
        const auto f = [i]() {
            i = 321; // error: cannot assign to a variable captured by copy in a non-mutable lambda
        };
        f();
    }
    Il faut donc écrire "auto f = [i]() mutable {" dans ce cas. Un truc qui est a peut prêt sur, c'est que mettre const et mutable ensemble, ça n'aurait pas de sens. (Et je suis pas sur du tout que ce soit possible, de toute façon).

    Donc sans mutable, cela veut dire que la lambda ne modifie pas son état interne. C'est ce que disait Bktero :

    Citation Envoyé par Bktero Voir le message
    la variable étant en quelque sorte "implicitement const"
    Et donc on pourrait tout a fait avoir la règle : soit const, soit mutable.

    Citation Envoyé par Bktero Voir le message
    J'ai l'impression que personne n'a de bons arguments pour ne pas mettre const
    Pour être honnête, je pense que le seul argument, c'est juste "par habitude". Il faudrait voir différents projets, pour voir ce qui se fait, mais j'ai l'impression que c'est juste "on fait pas comme ça", sans raison technique spécifique.

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 632
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 711
    Points
    30 711
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Mouais. C'est quand même tiré par les cheveux de faire une telle distinction. cppreference parle même de "unnamed function object" pour désigner une lambda. A mon sens, l'argument de ternel est complètement vailde.
    C'est tout aussi tiré par les cheveux de penser que n'importe quelle expression peut être considérée comme un objet pouvant être constant ou non


    Pour "modifier ces variables internes" (c'est à dire les captures), il faut de toute façon explicitement mettre "mutable".


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
     
    int main() {
        int i = 123;
        const auto f = [i]() {
            i = 321; // error: cannot assign to a variable captured by copy in a non-mutable lambda
        };
        f();
    }
    Sauf que tu auras exactement le même résultat avec f déclarée sous la forme de auto f = [i](){ i = 456;}; ... La preuve ==>ICI<==...

    Le tout, sans même mentionner les règles d'application du mot clé const, qui, par défaut, s'applique à ce qui se trouve à sa gauche, sauf si rien ne le précède, car dans ce cas, il s'applique à ce qui le suit (se trouve à sa droite).

    Le mot clé const s'applique donc dans le cas présent à auto, qui correspond à ce que l'expression lambda est sensée renvoyer (si, du moins, elle renvoie quelque chose), et non à f (qui est l'alias sur l'expression)

    Il faut donc écrire "auto f = [i]() mutable {" dans ce cas. Un truc qui est a peut prêt sur, c'est que mettre const et mutable ensemble, ça n'aurait pas de sens. (Et je suis pas sur du tout que ce soit possible, de toute façon).

    Donc sans mutable, cela veut dire que la lambda ne modifie pas son état interne. C'est ce que disait Bktero :



    Et donc on pourrait tout a fait avoir la règle : soit const, soit mutable.



    Pour être honnête, je pense que le seul argument, c'est juste "par habitude". Il faudrait voir différents projets, pour voir ce qui se fait, mais j'ai l'impression que c'est juste "on fait pas comme ça", sans raison technique spécifique.[/QUOTE]

  10. #10
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    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 196
    Points : 17 165
    Points
    17 165
    Par défaut
    A vrai dire, je considère ce cas completement théorique: je ne mets jamais une lambda dans une variable locale.

    Et s'il y a bien une chose que je trouve dommage en C++, c'est qu'il faille préciser const plutot que modifiable. Aujourd'hui, je préfèrerai qu'un nom soit une constante par défaut.

    Bref, j'ai vaguement un argument de cohérence. Comme j'essaie de toujours déclarer mes variables locale auto const, je ferai de même pour une lambda.

  11. #11
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    397
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 397
    Points : 698
    Points
    698
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Sauf que tu auras exactement le même résultat avec f déclarée sous la forme de auto f = [i](){ i = 456;}; ... La preuve ==>ICI<==...
    Jamais dit le contraire.

    Citation Envoyé par koala01 Voir le message
    C'est tout aussi tiré par les cheveux de penser que n'importe quelle expression peut être considérée comme un objet pouvant être constant ou non
    Jamais dit cela. Une expression est une expression. C'est un terme qui est clairement définie dans le langage C++ (https://en.cppreference.com/w/cpp/language/expressions) : "An expression is a sequence of operators and their operands, that specifies a computation."

    Une lambda est un objet. Et à même une existence en mémoire.

    A mon avis, tu mélange l'expression de la lambda (c'est a dire la syntaxe `[](){}`) et l'objet qui est construit par cette expression.

    Bref, peu importe. L'argument qu'une lambda serait une expression et que cela justifierait de pas mettre const me semble pas très solide.

    Citation Envoyé par ternel Voir le message
    A vrai dire, je considère ce cas completement théorique: je ne mets jamais une lambda dans une variable locale.
    Les cas où j'ai vu ou fait cela, c'est juste pour la lisibilité du code, pour "sortir" une lambda d'un code trop lourd à lire. Et a ma connaissance, le compilateur optimise cela, ce qui revient à ne pas créer de variable locale (avec ou sans const).

    Donc purement de la présentation du code. Et c'est donc probablement pour cela que personne se pose trop la question de mettre const ou pas.

    D'autant plus que mettre const n'apporte rien à la qualité du code. Parce que comme l'a dit Bktero, on ne peut de toute façon pas modifier f, même sans const. Le const n'apporte aucune garantie supplémentaire.

    Citation Envoyé par ternel Voir le message
    Bref, j'ai vaguement un argument de cohérence. Comme j'essaie de toujours déclarer mes variables locale auto const, je ferai de même pour une lambda.
    C'est un argument tout à fait valide.

  12. #12
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 486
    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 486
    Points : 13 696
    Points
    13 696
    Billets dans le blog
    1
    Par défaut
    Je suis au regret de vous annoncer que ce code est valide (mais tu ne peux pas exécuter ton functor...) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main(){
        int i = 345;
        const auto f = [i]() mutable { i = 12;};
    }
    Par ailleurs, f est bien une variable. Il suffit de regarder ce code dans CppInsights (https://cppinsights.io) pour s'en convaincre.

    Je n'ai peut-être pas parfaitement préciser ma question (je crois surtout que je n'avais pas cet aspect en tête), mais l'ajout (ou pas) de const n'était pas en premier lieu motivé par une question de const-correctness pour s'assurer qu'on n'appelle que des const member functions. C'était surtout par rapport au fait qu'on pouvait réaffecter la variable, ce qui est plus générale comme contrainte, car ça s'applique aussi aux base types (pointeurs inclus).

  13. #13
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    397
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 397
    Points : 698
    Points
    698
    Par défaut
    Oui, je n'avais pas précisé que dans le code que j'avais donné, c'était a l'exécution de f que cela posait problème quand on avait mutable et const. Je n'ai pas détaillé tous les cas et tous les messages d'erreur.

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 632
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 711
    Points
    30 711
    Par défaut
    Citation Envoyé par ternel Voir le message
    . C'est un terme qui est clairement définie dans le langage C++ (https://en.cppreference.com/w/cpp/language/expressions) : "An expression is a sequence of operators and their operands, that specifies a computation."
    Oui...

    As tu simplement remarqué que c'est exactement la description d'un comportement

    Car, quelle que soit l'expression envisagée, son but restera de fournir un comportement... Aller, quelques exemples "au hasard":
    • int a; ==>comportement désigné : déclarer une variable nommée a de type int
    • a= b+ c;==>comportement désigné : assigner à la variable a le résultat de l'expression b + c
    • obj.maFonction(/* parametres*/); ==> comportement désigné : invoquer la fonction membre nommée "maFonction" en lui transmettant les paramètres indiqués
    • truc = foo(obj) ==>comportement désigné : assigner à truc la valeur renvoyée par l'invocation la fonction libre nommé foo à laquelle on a passé obj comme paramètre

    Je pourrais multiplier les exemples à l'envie, en utilisant toutes les techniques possibles et imaginables: les expressions désignent des comportements que le compilateur devra invoquer d'une manière ou d'une autre. Soit parce qu'il devra exécuter lui-même le comportement, soit parce qu'il devra générer un code binaire exécutable équivalent permettant de faire appel à ces comportement à l'exécution du programme.
    Citation Envoyé par ternel
    Une lambda est un objet. Et à même une existence en mémoire.
    Oui, tu as aussi raison: une expression lambda a *** forcément *** une existance en mémoire... Mais... Laquelle

    Est-ce la même existence en mémoire que celle d'une donnée (comme un variable de type Point) ou plutôt celle d'une fonction

    Et oui, tu as encore raison: une lambda est un objet. En dehors de toute précision, je n'ai pas d'autre choix que d'admettre cette assertion.

    Le problème, vois tu, c'est que si cppreference parle de unnammed function object pour les expressions lambda, c'est que chaque mot compte, et que si tu "fais l'impasse" sur l'un de ces mots, tu vas naturellement te mettre à penser à quelque chose qui ne correspond pas à la réalité.

    En fait, on est confronté à un problème fort similaire à celui que l'utilisation du terme "polymorphisme". Pour rappel, si on utilise le terme "polymorphisme" sans autre précision, on va "naturellement" penser au polymorphisme d'inclusion, celui qui nous permet d'écrire un code proche
    de
    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
    class Base{
    public:
        virtual void foo(){ std::cout<<"foo() called from Base\n";}
    };
    class Derived : public Base{
    public:
        void foo() final override{std::cout<<"foo() called from Derived\n";}
    };
    void bar(Base & b){
       b.foo();
    }
    int main(){
        Base b;
        Derived d;
        foo(b); // "foo() called from Base"
        foo(d); // "foo() called from Derived"
    }
    Parce que c'est sans doute le polymorphisme sur lequel on insiste le plus lors de la formation.

    On oublie simplement que le terme polymorphisme signifie "la capacité d'adapter son comportement aux différentes situations", et que, à ce titre, il existe quatre types différents de polymorphismes, à savoir
    • la surcharge: deux fonctions prenant des paramètres différents mais portant le même non sont "polymorphes" dans le sens où elles décrivent un comportement identique (adapté aux circonstance) void foo(Type1); et void foo(AutreType);
    • la coercission: le fait d'adapter l'un des paramètre pour l'adapter au besoin de l'autre: int a = 2; double b = 3.1415; double resultat = a + b; //<==polymorphisme de coercission
    • le polymorphisme d'inclusion : c'est le polymorphisme auquel on pense généralement
    • le polymorphisme paramétrique: c'est le genre de polymorphisme que l'on peut obtenir au travers de la programmation générique template <typename T> void foo(T t); //<== polymorphisme paramétrique: le comportement s'adapte au type réel de T


    Hé bien, il faut bien se rendre compte que l'on a exactement le même problème lorsque l'on parle "d'objet". Sans autre précision, les gens vont naturellement penser à des "objets données", des objets que l'on peut manipuler à notre guise, et pour lesquels on peut même envisager de modifier les données internes auxquelles nous aurions accès.

    Bref, les gens vont penser à ceci en priorité:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct Point{
        int x;
        int y;
        void foo(){/* ...*/}
    };
    void bar(Point /* const &*/ lePoint);
    int main(){
       Point p{2,5}; // je crée un objet 
       p.x*=10; // je modifie mon objet, parce que j'en ai l'occasion ;)
       p.foo(); // j'appelle la fonction membre
       bar(p); //  j'appelle la fonction libre   
    }
    parce que c'est comme cela qu'on nous a présenté la notion d'objet ... depuis le premier cours où on l'a utilisée...

    Sauf que la notion de foncteur (d'abord) puis celle d'expression lambda sont venu mettre le foutoir là dedans; l'expression lambda n'étant qu'un sucre syntaxique nous facilitant la vie pour la création de foncteur. Les foncteurs n'étant en définitive qu'une adaptation du problème lié à l'utilisation des pointeurs sur fonction.

    Or, qu'il s'agisse de pointeur de fonction, de foncteur ou d'expression lambda, il est important que l'on ne s'intéresse plus à l'aspect "donnée" du problème, mais qu'on réfléchit à l'aspect "fonction" du problème, à son aspect comportemental.

    Et le truc, c'est qu'on ne peut pas se permettre de mélanger cette noton "comportementale" avec les notion "données". Cette discussion en est la preuve: à mélanger les deux sans vergogne, on finit par ne plus se rendre compte du mur qui sépare les données des comportmements, et par s'étonner du fait que le compilateur n'est pas d'accord lorsqu'on essaye de faire passer un comportement pour une donnée...

    Avant d'aller plus loin, je voudrais attirer votre attention sur une chose un peu surprenante: voyez ce qui se passe lorsqu'on déplace simplement la déclaration de l'expression lambda:
    avec le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #include <iostream>
    int main(){
        int i =15;
        for(i = 0;i<10;++i){
            auto f = [i]()->auto{ return i*10;};
            std::cout<<f()<<"\n";
        }
    }
    les valeurs affichées évoluent (comme on pourrait s'y attendre) au rythme de la boucle, que ce soit avec gcc ou clang

    Par contre, si on sort la déclaration de l'expression lambda de la boucle, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
     
    int main(){
        int i =15;
        auto f = [i]()->auto{ return i*10;};
        for(i = 0;i<10;++i){
            std::cout<<f()<<"\n";
        }
    }
    alors, pour chaque passage dans la boucle, on obtient la même valeur qui correspond à 15 *10, et ce, aussi bien avec gcc qu'avec clang.
    (Et pour aller encore plus loin, on ne peut pas déclarer l'expression lambda en dehors de la fonction main, car la variable à variable à capturer est inexistante (ce qui résulte en une erreur de compilation) Mais bon, je m'y attendais aussi...

    Cela semble indiquer que la capture, dépend du contexte dans lequel la déclaration de l'expression apparait, et ce, même si les (éventuels paramètres) dépendront du contexte dans lequel l'appel sera effectué.


    [/QUOTE=ternel]A mon avis, tu mélange l'expression de la lambda (c'est a dire la syntaxe `[](){}`) et l'objet qui est construit par cette expression.[/QUOTE]
    Justement non, je ne mélange absolument rien, car c'est intrinsèquement lié...
    [](){} est la syntaxe de déclaration d'une expression lambda. Elle est utilisée pour déclarer une fonction objet. Pas un objet "classique"! une fonction objet. C'est à dire un objet comportemental. Et donc, qui n'a rien à voir avec les objet "classiques", que l'on pourrait qualifier "d'objet données". Tant que l'on prendra le risque de supprimer des mots qui nous semblent ne servir à rien dans les concepts qu'on utilise, on se retrouvera sytématiquement à mélanger des torchons avec des serpillières!


    Et, de même, lorsque l'on écrit le code auto f = [](){};, ce que l'on fait, c'est imposer un identifiant (f) à quelque chose qui n'en a théoriquement pas (vu qu'il s'agit d'une fonction objet anonyme). Mais cela n'enlève absolument rien à l'apect comportemental de l'expression lambda.

    Et surtout, en tant qu'objets (n'étant pas des données), les expressions et les fonctions sont forcément constantes "par nature", vu qu'elles sont évaluée ... à la compilation


    Citation Envoyé par ternel
    Les cas où j'ai vu ou fait cela, c'est juste pour la lisibilité du code, pour "sortir" une lambda d'un code trop lourd à lire. Et a ma connaissance, le compilateur optimise cela, ce qui revient à ne pas créer de variable locale (avec ou sans const).
    A ceci près que ce n'est pas "une optimisation consistant à éviter la création d'une donnée", parce qu'il n'y a pas de donnée qui entre en jeu... il n'y a qu'un comportement, qu'une fonction qui pourrait tout aussi bien être inlinée si les conditions sont remplies...

    C'est pour cela que j'insiste: on peut parler d'objet, car cela en a toutes les caractéristiques, mais on ne peut pas parler de donnée car ca a les caractéristiques ... d'un comportement. Et il est primordial de clairement séparer la notion d'objet "donnée" de la notion d'objet "fonction" par un mur infranchissable!
    Donc purement de la présentation du code. Et c'est donc probablement pour cela que personne se pose trop la question de mettre const ou pas.
    Et c'est pour cela que la réponse à apporter est plutôt de l'ordre de
    Tu n'as pas besoin de t'en inquiéter parce que les lignes de conduites incitent à mettre les objets de la catégorie "données" constants autant que faire se peut, alors qu'une expression lambda est un objet de la catégorie "comportements", qui est -- par nature -- évalué à la compilation et donc, forcément constante (car le seul moyen de la modifier est de modifier le code)
    On devrait d'ailleurs surtout regretter l'utilisation ambigüe du terme d'"objet", étant donné qu'il représente deux notions incompatibles par nature...

    Citation Envoyé par ternel Voir le message
    A vrai dire, je considère ce cas completement théorique: je ne mets jamais une lambda dans une variable locale.

    Et s'il y a bien une chose que je trouve dommage en C++, c'est qu'il faille préciser const plutot que modifiable. Aujourd'hui, je préfèrerai qu'un nom soit une constante par défaut.

    Bref, j'ai vaguement un argument de cohérence. Comme j'essaie de toujours déclarer mes variables locale auto const, je ferai de même pour une lambda.
    Citation Envoyé par Bktero Voir le message
    Je suis au regret de vous annoncer que ce code est valide (mais tu ne peux pas exécuter ton functor...) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main(){
        int i = 345;
        const auto f = [i]() mutable { i = 12;};
    }
    Par ailleurs, f est bien une variable. Il suffit de regarder ce code dans CppInsights (https://cppinsights.io) pour s'en convaincre.

    Je n'ai peut-être pas parfaitement préciser ma question (je crois surtout que je n'avais pas cet aspect en tête), mais l'ajout (ou pas) de const n'était pas en premier lieu motivé par une question de const-correctness pour s'assurer qu'on n'appelle que des const member functions. C'était surtout par rapport au fait qu'on pouvait réaffecter la variable, ce qui est plus générale comme contrainte, car ça s'applique aussi aux base types (pointeurs inclus).
    Je ne sais pas ce que tu voulais montrer avec le lien, peut-être t'es tu planté en le collant, cependant, le fait de mélanger la définition d'une expression lambda avec celle d'un tableau de caractère n'apporte absolument aucune preuve, car, comme j'ai essayé de le faire passer durant toute cette intervention, les deux notions parlent de choses totalement différentes et les mélanger (comme tu semble le faire) revient à mélanger les essuies et les serpillières

  15. #15
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    397
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 397
    Points : 698
    Points
    698
    Par défaut
    TL;DR. Ajouter un nouveau concept "comportement" (qui n'existe pas dans le standard), c'est juste de l'explication ad-hoc. La comparaison avec le polymorphisme n'a pas de sens. Le concept "object" est clairement définie (https://en.cppreference.com/w/cpp/language/object) et cela correspond bien a l'objet qui est créé par une expression lambda. "Function object" est aussi clairement définie (https://en.cppreference.com/w/cpp/utility/functional), c'est juste un objet qui peut être appelé. C'est juste toi qui créé ce concept de objet donnée et qui rend ambiguë les choses.

    Bref. Je pense qu'on a donné nos points de vue sur la question initiale de Bktero. (Pour résumer, soit utiliser const pour suivre la guideline "const par defaut". Soit pas const parce qu'on a pris l'habitude de faire comme ca. Et koala qui avance un argument technique contre const, que chacun est libre de juger de la pertinence) . Ca sert à rien de tourner en rond plus longtemps.

  16. #16
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 486
    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 486
    Points : 13 696
    Points
    13 696
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je ne sais pas ce que tu voulais montrer avec le lien, peut-être t'es tu planté en le collant, cependant, le fait de mélanger la définition d'une expression lambda avec celle d'un tableau de caractère n'apporte absolument aucune preuve, car, comme j'ai essayé de le faire passer durant toute cette intervention, les deux notions parlent de choses totalement différentes et les mélanger (comme tu semble le faire) revient à mélanger les essuies et les serpillières
    Si tu n'arrives pas à voir que c'est un lien vers la page sans partage de code (car j'avais pas trouvé le bouton pour le faire) et que le code présenté est juste le code par défaut de l'outil, je me demande ce que je dois penser...

    Voici un lien qui montre mon snippet précédent (il faut quand même cliquer sur PLAY).

    Quand il y a une classe et une instance d'une classe, on peut écrire 4 kilomètres pour dire ce qu'on veut, il y a indéniablement un objet.

    pour mintho carmo

  17. #17
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    Bonsoir,

    const apporte une garantie pour les humains sur les codes qui évoluent.

    Supposons que l'on parte d'un gentil

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    auto compute_normal = [](Point p) { return ...};
    ...
    for (auto const& point : points) {
        auto n = compute_normal(point);
    ...

    Puis on se rend compte que c'est plus subtil et que l'on se mettre à faire de la lambda-ception.


    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    auto normal_ceptor = [] (double nodata, double spacing) {
        // des calculs
        ...
        auto normal_computer = [=les_options] (Point p) { return ...};
        return normal_computer;
    };
     
    auto compute_normal = normal_ceptor(get_nodata(), get_spacing());
    ...
    for (auto const& point : points) {
        auto n = compute_normal(point);
    ...

    Et puis... on évolue encore. On doit introduire des branches, et selon la branche, le normal_ceptor renvoie des lambdas différentes. Il doit donc évoluer. Il ne peut que renvoyer un `std::function`. Et sa signature devient:

    Code "cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    auto normal_ceptor = [=] (double nodata, double spacing)
     -> std::function<Point (Point)>
    { ... };

    Et là... notre `compute_normal`... c'est plus une lambda. C'est une std::function. Si on est cohérent avec un style const-everything-we-can, cette fois-ci, sans le const, il est mutable. Et donc, on le met quand le const? Quand on passe par le lambda-ceptor? Quand le lambda-ceptor est sorti de la fonction fourre-tout (histoire de s'en servir depuis 42 autres endroits)? Quand le lambda-ceptor se met à renvoyer des std::functions? (et qu'il est déjà appelé depuis 42 autres endroits?).

    Ma tendance, c'est d'accepter le vocabulaire "object-function", et "object" du standard. Et donc de mettre des const dès le début, si je veux être cohérent.

  18. #18
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    397
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 397
    Points : 698
    Points
    698
    Par défaut
    Je trouve l'argument intéressant. Effectivement, mettre `const` permet d'éviter de perdre la constance quand on passe d'un foncteur "implicitement const" comme les lambdas (pour reprendre les termes de Bktero) à un foncteur non "implicitement const" comme les `std::function`.

    Par contre, on est dans un cas un peu particulier. C'est plus une lambda qu'on met directement dans `auto` mais le résultat de l'execution d'une lambda. C'est une problématique générale d'utilisation de `auto` pour des retours de fonctions, le type peut changer et produire des surprises. C'est pas une problématique spécifiques aux lambdas.

    On n'est plus dans le cas d'utilisation dont j'ai parlé, a savoir utiliser une variable locale pour simplement présenter le code. (Ce qui est, à mon avis, la cas majoritaire d'utilisation de `auto` avec des lambdas).

  19. #19
    Membre émérite Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 047
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 047
    Points : 2 254
    Points
    2 254
    Par défaut
    Je n'ai pas trop envie de rentrer dans un débat stérile, alors pour ceux qui veulent, une petite lecture peut-être nécessaire sur le "const partout".

    https://quuxplusone.github.io/blog/2...ll-the-things/

    Pour ma part, les projets où les gens débataient sur le const ou pas, généralement, ce n'était pas le problème principale du projet.

  20. #20
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    const, ce n'est pas tant une histoire de prévenir les bugs sur les variables locales -- si on excepte le cas des paramètres sortants qui sont pris par référence et pas par pointeur (j'ai horreur par pointeur). (Pour ceux qui ont besoin d'explicite, je préfère de loin un décorateur de type genre `out<>` à constructeur explicite)

    Pour moi, const sur les variables locales, c'est une aide à l'analyse de code des jours/semaines/mois/années après. Que cela soit pour comprendre ce que fait un algo que pour débugguer (ce qui revient un peu au même) => on sait alors que quand on déroule pas à pas (/à la main) un code pour comprendre ce qu'il fait/ce qui cloche, et bien il y a des données dont on n'a plus besoin de surveiller l'évolution. Et ça c'est cool.

    C'est juste pas compatible avec le NRVO

Discussions similaires

  1. Réponses: 10
    Dernier message: 08/11/2010, 00h53
  2. Auto completion pour les facelets xhtml
    Par BugFactory dans le forum Eclipse Java
    Réponses: 1
    Dernier message: 17/09/2009, 11h24
  3. Réponses: 1
    Dernier message: 24/06/2009, 11h37
  4. Auto-complétion pour les mots clés Begin/End
    Par Alex Laforest dans le forum EDI
    Réponses: 2
    Dernier message: 21/09/2005, 22h26

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