ambiguité sur le mot clé mutable

Le problème exposé dans ce sujet a été résolu.

Salut tous le monde,

Je viens de terminer la lecture du chapitre sur les lambdas, et j’avoue avoir appris bcp de choses lors de sa lecture. En le lisant et en faisant des petits tests, j’ai pris quelques notes :

  • Une variable capturée par copie est de type constante peu importe si la variable que la lambda capture est constante ou non.

  • Si la variable que la lambda capture (par copie) est de type non-constante

    • alors on peut modifier la variable capturée si on utilise le mot clé mutable
  • Si la variable que la lambda capture (par copie) est de type constante

    • alors on ne peut pas modifier la variable capturée même si le mot clé mutable est utilisé

Mtn voici le code qui est à l’origine de deux derniers remarques ci-dessous :

#include <iostream>

int main ()
{
    int const entier { 3 };

    auto lambda { [entier] () mutable -> void
        {
            entier = 6;
            std::cout << "entier lambda vaut : " << entier << std::endl; 
        }
    };

    lambda ();

    return 0;
}

Dans la première remarque, j’ai eu une explication du cours en citant Herb Sutter et cela m’a suffit. En ce qui concerne la deuxième remarque, je la trouve logique, normale. Alors que la troisième remarques, je ne vois pas la logique qui est derrière ceci : après tout, dans les 2 cas c’est une copie qu’on essaye de modifier avec mutable)

J’espère avoir été clair sur mon incompréhension, et que qqn pourra m’expliquer en des termes simples ceci. Merci pour votre temps.

+0 -0

Salut,

Alors que la troisième remarques, je ne vois pas la logique qui est derrière ceci : après tout, dans les 2 cas c’est une copie qu’on essaye de modifier avec mutable)

Ça te choque vraiment qu’une constante ne puisse pas être mutée ? Une constante n’est pas mutable par définition… Si on commence à autoriser des modifications de constantes, on peut jeter le concept de constante dans son ensemble. La raison d’être des constantes en programmation est d’offrir la garantie que la valeur associée ne varie pas dans le programme.

Non ça ne me choque pas.

Je comprends que la lambda capture (par défaut) une copie constante de la variable extérieure qu’elle veut capturer et que le mot clé mutable rend la modification possible.

Peu importe que la variable extérieur soit constante ou non, la lambda capture une copie constante, donc c’est une copie de la variable extérieure donc il ne s’agit pas de la même variable alors pourquoi c’est impossible de la modifier avec mutable si la variable extérieure est constante (puisque c’est sa copie qu’on veut modifier pas la variable extérieure)

Et je comprends très bien qu’on déclare une variable const afin de ne pas changer sa valeur. Je crois que je me suis mal expliqué, désolé le français ce n’est pas ma langue maternelle.

+0 -0

Une lambda s’intéresse aux variables de son scope plutôt qu’à leur valeur (sinon on fait un passage par argument plutôt qu’une capture). Quand tu écris [entier], ce que tu veux est capturer la variable entier (et son type) depuis le scope dans lequel tu définis la lambda. Peu importe que cette capture se fasse par copie de la valeur ou non, c’est un détail d’implémentation du concept de lambda (ou plutôt de closure) dans C++. Si la variable que tu captures est une constante, tu n’as aucune envie d’autoriser sa modification.

EDIT : regarde ce code

#include<iostream>

int main() {
    int entier { 3 };
    auto lambda {
        [entier] () mutable -> void {
            entier += 6;
            std::cout << entier << std::endl;
        }
    };
    lambda();
    lambda();
    std::cout << entier << std::endl;
    return 0;
}

Ce code affiche

9
15
3

Si tu captures une constante mais que tu la modifies, quel est le comportement que tu voudrais ? Tu as capturé un int const, mais sa valeur change d’une exécution de la fonction à l’autre ? Ce serait surprenant…

+2 -0

je vais revenir plus tard sur ce point, pour l’instant je comprends c’est un détails d’implémentation de lambda en C++.

C’est loin d’être le point important de mon commentaire, as tu vu mon edit ? Si tu remplaces la ligne 4 par int const entier {3}, quel comportement souhaiterais tu ? Tu captures une variable de type int const mais tu voudrais que la valeur capturée change à chaque appel ?

Non, je viens de lire maintenant ton édit, je comprends mieux mtn : une lambda capture la variable extérieure et son type tel qu’il est. Je croyais qu’elle capturait la variable et son type sans le const tout comme les paramètres d’une fonction.

Si tu remplaces la ligne 4 par int const entier {3}, quel comportement souhaiterais tu ? Tu captures une variable de type int const mais tu voudrais que la valeur capturée change à chaque appel ?

adri1

Alors là je trouve normale (et je m’attendrais) d’avoir une erreur puisque la lambda capture aussi son type tel qu’il est (int const) et qu’en plus à chaque appel de la lambda, entier est incrémenté de 6 or ce n’est pas possible.

Merci pour le code, j’avoue que je suis surpris de voir qu’à chaque appel de la lambda, entier soit incrémenté de 6, on dirait que la lambda mémorise la valeur précédente de la variable entier tout comme une boucle.

+0 -0

Merci pour le code, j’avoue que je suis surpris de voir qu’à chaque appel de la lambda, entier soit incrémenté de 6, on dirait que la lambda mémorise la valeur précédente de la variable entier tout comme une boucle.

C’est effectivement ce que fait la lambda (enfin plus exactement la closure). Quand tu captures une variable, tu l’ajoutes dans le scope de la closure avec son type, et ce scope est persistant tant que la closure existe. En pratique, les captures servent justement à conserver (et si besoin modifier) un état d’un appel à l’autre de la fonction. Une capture n’a, de fait, pas grand chose à voir avec un argument de fonction classique.

Il est extrêmement simple d’implémenter par exemple un itérateur en se servant d’une lambda qui capture un compteur et l’incrémente à chaque appel.

+2 -0

Je viens de faire une petite recherche sur une closure. D’après ce que j’ai lu, une closure (fermeture) est une fonction accompagnée de son environnement lexicale (scope) et que son scope contient l’ensemble des variables non-locales qu’elle a capturée par valeur ou par référence.

Et je crois qu’elle utilise le mécanisme de la pile pour mémoriser la variable capturée et modifier a chaque appel de la lambda.

Il est extrêmement simple d’implémenter par exemple un itérateur en se servant d’une lambda qui capture un compteur et l’incrémente à chaque appel.

adri1

Comme ceci :

#include <iostream>
#include <vector>

int main ()
{
    std::vector<int> const v {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    };

    auto it { v.cbegin () };

    auto lambda { [& v, & it] () -> void
        {
            std::cout << * it << std::endl;
            ++ it;
        }
    };

    while (it != v.cend ())
    {
        lambda ();
    }

    return 0;
}
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte