Utilisation d'une variable membre dans la liste de capture lambda à l'intérieur d'une fonction membre

145

Le code suivant se compile avec gcc 4.5.1 mais pas avec VS2010 SP1:

#include <iostream>
#include <vector>
#include <map>
#include <utility>
#include <set>
#include <algorithm>

using namespace std;
class puzzle
{
        vector<vector<int>> grid;
        map<int,set<int>> groups;
public:
        int member_function();
};

int puzzle::member_function()
{
        int i;
        for_each(groups.cbegin(),groups.cend(),[grid,&i](pair<int,set<int>> group){
                i++;
                cout<<i<<endl;
        });
}
int main()
{
        return 0;
}

Voici l'erreur:

error C3480: 'puzzle::grid': a lambda capture variable must be from an enclosing function scope
warning C4573: the usage of 'puzzle::grid' requires the compiler to capture 'this' but the current default capture mode does not allow it

Alors,

1> quel compilateur a raison?

2> Comment puis-je utiliser des variables membres dans un lambda dans VS2010?

vivek
la source
1
Remarque: cela devrait être pair<const int, set<int> >, c'est le type de paire réel d'une carte. Il devrait également s'agir d'une référence à const.
Xeo
En relation; très utile: thispointer.com
Gabriel Staples

Réponses:

157

Je pense que VS2010 a raison cette fois-ci, et je vérifierais si j'avais la norme à portée de main, mais actuellement je ne l'ai pas.

Maintenant, c'est exactement comme le message d'erreur dit: Vous ne pouvez pas capturer des éléments en dehors de la portée englobante du lambda. grid n'est pas dans la portée englobante, mais thisest (chaque accès à gridse produit en fait comme this->griddans les fonctions membres). Pour votre cas d'utilisation, la capture thisfonctionne, car vous l'utiliserez tout de suite et vous ne voulez pas copier legrid

auto lambda = [this](){ std::cout << grid[0][0] << "\n"; }

Si toutefois vous souhaitez stocker la grille et la copier pour un accès ultérieur, là où votre puzzleobjet peut déjà être détruit, vous devrez faire une copie locale intermédiaire:

vector<vector<int> > tmp(grid);
auto lambda = [tmp](){}; // capture the local copy per copy

† Je simplifie - Google pour "atteindre la portée" ou voir §5.1.2 pour tous les détails sanglants.

Xeo
la source
1
Cela me semble assez limité. Je ne comprends pas pourquoi un compilateur devrait empêcher une telle chose. Cela fonctionne bien avec bind, bien que la syntaxe soit horrible avec l'opérateur de décalage gauche ostream.
Jean-Simon Brochu
3
Peut - tmpêtre const &à gridde réduire la copie? Nous voulons toujours au moins une copie, la copie dans le lambda ( [tmp]), mais pas besoin d'une deuxième copie.
Aaron McDaid
4
La solution peut créer une copie supplémentaire inutile, gridbien qu'elle soit probablement optimisée. Plus court et mieux c'est: auto& tmp = grid;etc.
Tom Swirly
4
Si vous disposez de C ++ 14, vous pouvez [grid = grid](){ std::cout << grid[0][0] << "\n"; }éviter la copie supplémentaire
sigy
Il semble être corrigé dans gcc 4.9 (et gcc 5.4 d'ailleurs)error: capture of non-variable ‘puzzle::grid’
BGabor
108

Résumé des alternatives:

capturer this:

auto lambda = [this](){};

utiliser une référence locale au membre:

auto& tmp = grid;
auto lambda = [ tmp](){}; // capture grid by (a single) copy
auto lambda = [&tmp](){}; // capture grid by ref

C ++ 14:

auto lambda = [ grid = grid](){}; // capture grid by copy
auto lambda = [&grid = grid](){}; // capture grid by ref

exemple: https://godbolt.org/g/dEKVGD

Trass3r
la source
5
Il est intéressant de noter que seule l'utilisation explicite de la capture avec la syntaxe d'initialisation fonctionne pour cela (c'est-à-dire qu'en C ++ 14, le faire [&grid]ne fonctionne toujours pas). Très content de le savoir!
ohruunuruus
1
Bon résumé. Je trouve la syntaxe C ++ 14 très pratique
tuket
22

Je crois que vous devez capturer this.

Michael Krelin - hacker
la source
1
C'est correct, il capturera le pointeur this et vous pouvez toujours vous y référer griddirectement. Le problème étant, que faire si vous souhaitez copier la grille? Cela ne vous permettra pas de faire cela.
Xeo
9
Vous pouvez, mais seulement d'une manière détournée: Vous devez faire une copie locale, et la capture que dans le lambda. C'est juste la règle avec les lambdas, vous ne pouvez pas capturer raide en dehors de la portée englobante.
Xeo
Bien sûr, vous pouvez copier. Je voulais dire que vous ne pouvez pas le copier-capturer, bien sûr.
Michael Krelin - hacker
Ce que j'ai décrit fait une capture de copie, via la copie locale intermédiaire - voir ma réponse. En dehors de cela, je ne connais aucun moyen de copier la capture d'une variable membre.
Xeo
Bien sûr, il copie la capture, mais pas le membre. Cela implique deux copies à moins que le compilateur ne soit plus intelligent que d'habitude, je suppose.
Michael Krelin - hacker
14

Une autre méthode qui limite la portée du lambda plutôt que de lui donner accès à l'ensemble thisest de passer une référence locale à la variable membre, par exemple

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
la source
J'adore votre idée: utiliser une fausse variable de référence et la transmettre à la liste de capture :)
Emadpres