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

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

C++ : auto ou const auto pour les lambdas ? Pourquoi ?
Par Bktero

Le , par Bktero

0PARTAGES

17  0 
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
1
2
3
4
int main() {
    auto f = []() {};
    f = []() {};
}
Code : Sélectionner tout
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
1
2
3
4
5
int main() {
    auto f = []() {};
    auto g = f;
    f = g;
}
Code : Sélectionner tout
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 = []() {};
      |              ^

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de ternel
Expert éminent sénior https://www.developpez.com
Le 27/02/2024 à 12:56
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
4  0 
Avatar de ternel
Expert éminent sénior https://www.developpez.com
Le 19/02/2024 à 18:51
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
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  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 27/02/2024 à 17:00
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
3  0 
Avatar de ternel
Expert éminent sénior https://www.developpez.com
Le 27/02/2024 à 23:03
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.
3  0 
Avatar de Luc Hermitte
Expert éminent sénior https://www.developpez.com
Le 29/02/2024 à 14:21
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
3  0 
Avatar de Astraya
Membre chevronné https://www.developpez.com
Le 25/02/2024 à 21:13
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.
2  0 
Avatar de Bktero
Modérateur https://www.developpez.com
Le 27/02/2024 à 11:20
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
2  0 
Avatar de mintho carmo
Membre éclairé https://www.developpez.com
Le 27/02/2024 à 18:31
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
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
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.
2  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 27/02/2024 à 22:03
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
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]
2  0 
Avatar de mintho carmo
Membre éclairé https://www.developpez.com
Le 28/02/2024 à 18:17
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/ut...ity/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.
2  0