Petit exemple:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
La question: pourquoi avons-nous besoin du mutable
mot-clé? C'est assez différent du passage de paramètres traditionnel aux fonctions nommées. Quelle est la justification derrière?
J'avais l'impression que tout l'intérêt de la capture par valeur était de permettre à l'utilisateur de modifier le temporaire - sinon je ferais presque toujours mieux d'utiliser la capture par référence, n'est-ce pas?
Des éclaircissements?
(J'utilise MSVC2010 au fait. AFAIK cela devrait être standard)
const
par défaut!const
par défaut.Réponses:
Cela nécessite
mutable
parce que par défaut, un objet fonction doit produire le même résultat à chaque appel. C'est la différence entre une fonction orientée objet et une fonction utilisant une variable globale, effectivement.la source
void f(const std::function<int(int)> g)
. Comment suis-je assuré qu'ilg
est effectivement référentiellement transparent ?g
Le fournisseur de peut avoir utilisé demutable
toute façon. Donc je ne sais pas. D'un autre côté, si la valeur par défaut est non-const
, et que les gens doivent ajouterconst
au lieu d'mutable
objets de fonction, le compilateur peut réellement appliquer laconst std::function<int(int)>
partie etf
peut maintenant supposer queg
c'estconst
non?Votre code est presque équivalent à ceci:
Vous pourriez donc penser à lambdas comme générant une classe avec operator () qui est par défaut const à moins que vous ne disiez qu'elle est mutable.
Vous pouvez également considérer toutes les variables capturées à l'intérieur de [] (explicitement ou implicitement) comme des membres de cette classe: des copies des objets pour [=] ou des références aux objets pour [&]. Ils sont initialisés lorsque vous déclarez votre lambda comme s'il y avait un constructeur caché.
la source
const
oumutable
lambda s'il était implémenté en tant que types définis par l'utilisateur équivalents, la question est (comme dans le titre et développée par OP dans les commentaires) pourquoiconst
est la valeur par défaut, donc cela ne répond pas.La question est, est-ce "presque"? Un cas d'utilisation fréquent semble être de revenir ou de passer des lambdas:
Je pense que ce
mutable
n'est pas un cas de "presque". Je considère que "capture par valeur" comme "me permet d'utiliser sa valeur après la mort de l'entité capturée" plutôt que "me permet d'en changer une copie". Mais peut-être que cela peut être soutenu.la source
const
? À quoi sert-il?mutable
semble hors de propos ici, quandconst
n'est pas la valeur par défaut dans "presque" (: P) tout le reste de la langue.const
soit la valeur par défaut, au moins les gens seraient obligés de considérer la const-exactitude: /const
qu'ils puissent l'appeler, que l'objet lambda soit ou non const. Par exemple, ils pourraient le passer à une fonction prenant unstd::function<void()> const&
. Pour permettre au lambda de modifier ses copies capturées, dans les documents initiaux, les données des membres de la fermeture étaient définiesmutable
automatiquement en interne. Vous devez maintenant saisir manuellementmutable
l'expression lambda. Je n'ai cependant pas trouvé de justification détaillée.FWIW, Herb Sutter, membre bien connu du comité de normalisation C ++, fournit une réponse différente à cette question dans Lambda Correctness and Ergonomie Issues :
Son article explique pourquoi cela devrait être changé en C ++ 14. Il est court, bien écrit et mérite d'être lu si vous voulez savoir «ce qui est à l'esprit [des membres du comité]» en ce qui concerne cette caractéristique particulière.
la source
Vous devez réfléchir au type de fermeture de votre fonction Lambda. Chaque fois que vous déclarez une expression Lambda, le compilateur crée un type de fermeture, qui n'est rien de moins qu'une déclaration de classe sans nom avec des attributs ( environnement où l'expression Lambda a été déclarée) et l'appel de fonction
::operator()
implémenté. Lorsque vous capturez une variable à l'aide de la copie par valeur , le compilateur crée un nouvelconst
attribut dans le type de fermeture, vous ne pouvez donc pas le modifier dans l'expression Lambda car il s'agit d'un attribut "en lecture seule", c'est la raison pour laquelle ils appelons cela une " fermeture ", car d'une certaine manière, vous fermez votre expression Lambda en copiant les variables de l'étendue supérieure dans l'étendue Lambda.mutable
, l'entité capturée deviendra unnon-const
attribut de votre type de fermeture. C'est ce qui fait que les modifications apportées à la variable mutable capturée par valeur ne sont pas propagées à l'étendue supérieure, mais restent à l'intérieur du Lambda avec état. Essayez toujours d'imaginer le type de fermeture résultant de votre expression Lambda, qui m'a beaucoup aidé, et j'espère que cela peut vous aider aussi.la source
Voir ce projet , sous 5.1.2 [expr.prim.lambda], paragraphe 5:
Modifier le commentaire de litb: Peut-être qu'ils ont pensé à la capture par valeur afin que les changements extérieurs aux variables ne soient pas reflétés à l'intérieur du lambda? Les références fonctionnent dans les deux sens, c'est donc mon explication. Je ne sais pas si c'est bon.
Edit sur le commentaire de kizzx2: La plupart du temps quand un lambda doit être utilisé est comme foncteur d'algorithmes. Le
const
ness par défaut permet de l'utiliser dans un environnement constant, tout comme lesconst
fonctions normales qualifiées peuvent y être utilisées, mais pas les fonctions nonconst
qualifiées. Peut-être qu'ils ont juste pensé à le rendre plus intuitif pour ces cas, qui savent ce qui se passe dans leur esprit. :)la source
const
par défaut? J'ai déjà une nouvelle copie, il semble étrange de ne pas me laisser le changer - surtout que ce n'est pas quelque chose de mal principalement - ils veulent juste que j'ajoutemutable
.var
comme mot-clé pour permettre le changement et la constante comme valeur par défaut pour tout le reste. Maintenant, nous ne le faisons pas, nous devons donc vivre avec cela. OMI, C ++ 2011 est plutôt bien sorti, compte tenu de tout.n
n'est pas temporaire. n est membre de l'objet fonction-lambda que vous créez avec l'expression lambda. L'attente par défaut est que l'appel de votre lambda ne modifie pas son état, il est donc constant de vous empêcher de le modifier accidentellementn
.la source
Vous devez comprendre ce que signifie la capture! c'est capturer et non passer l'argument! regardons quelques exemples de code:
Comme vous pouvez le voir, même s'il
x
a été modifié,20
le lambda renvoie toujours 10 (x
est toujours5
à l'intérieur du lambda) Changerx
à l'intérieur du lambda signifie changer le lambda lui-même à chaque appel (le lambda mute à chaque appel). Pour appliquer l'exactitude, la norme a introduit lemutable
mot - clé. En spécifiant un lambda comme mutable, vous dites que chaque appel au lambda pourrait provoquer un changement dans le lambda lui-même. Voyons un autre exemple:L'exemple ci-dessus montre qu'en rendant le lambda mutable, changer
x
à l'intérieur du lambda "mute" le lambda à chaque appel avec une nouvelle valeurx
qui n'a rien à voir avec la valeur réelle dex
dans la fonction principalela source
Il existe maintenant une proposition pour alléger le besoin de
mutable
déclarations in lambda: n3424la source
mutable
même pas que c'est même un mot clé en C ++.Pour étendre la réponse de Puppy, les fonctions lambda sont censées être des fonctions pures . Cela signifie que chaque appel donné un ensemble d'entrée unique renvoie toujours la même sortie. Définissons l' entrée comme l'ensemble de tous les arguments plus toutes les variables capturées lorsque le lambda est appelé.
Dans les fonctions pures, la sortie dépend uniquement de l'entrée et non d'un état interne. Par conséquent, toute fonction lambda, si elle est pure, n'a pas besoin de changer son état et est donc immuable.
Lorsqu'un lambda capture par référence, l'écriture sur les variables capturées met à rude épreuve le concept de fonction pure, car tout ce qu'une fonction pure devrait faire est de renvoyer une sortie, bien que le lambda ne mute certainement pas car l'écriture se produit sur des variables externes. Même dans ce cas, une utilisation correcte implique que si le lambda est appelé à nouveau avec la même entrée, la sortie sera la même à chaque fois, malgré ces effets secondaires sur les variables by-ref. Ces effets secondaires ne sont que des moyens de renvoyer des entrées supplémentaires (par exemple, mettre à jour un compteur) et pourraient être reformulés en une fonction pure, par exemple en renvoyant un tuple au lieu d'une seule valeur.
la source