Verwenden der Mitgliedsvariablen in der Lambda-Erfassungsliste innerhalb einer Mitgliedsfunktion

145

Der folgende Code wird mit gcc 4.5.1 kompiliert, jedoch nicht mit 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;
}

Dies ist der Fehler:

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

So,

1> Welcher Compiler ist richtig?

2> Wie kann ich Mitgliedsvariablen in einem Lambda in VS2010 verwenden?

vivek
quelle
1
Hinweis: Es sollte sein pair<const int, set<int> >, dass dies der tatsächliche Paartyp einer Karte ist. Es sollte möglicherweise auch ein Verweis auf const sein.
Xeo
Verbunden; sehr hilfreich: thispointer.com/…
Gabriel Staples

Antworten:

157

Ich glaube, dass VS2010 diesmal richtig ist, und ich würde prüfen, ob ich den Standard zur Hand habe, aber derzeit nicht.

Jetzt ist es genau so, wie in der Fehlermeldung steht: Sie können keine Inhalte außerhalb des umschließenden Bereichs des Lambda erfassen. grid befindet sich nicht im umschließenden Bereich, ist es aber this(jeder Zugriff auf erfolgt gridtatsächlich wie this->gridin Mitgliedsfunktionen). Für Ihren Anwendungsfall thisfunktioniert das Erfassen , da Sie es sofort verwenden und das nicht kopieren möchtengrid

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

Wenn Sie das Raster jedoch speichern und für den späteren Zugriff kopieren möchten, wo Ihr puzzleObjekt möglicherweise bereits zerstört ist, müssen Sie eine lokale Zwischenkopie erstellen:

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

† Ich vereinfache - Google für "Erreichen des Geltungsbereichs" oder siehe §5.1.2 für alle wichtigen Details.

Xeo
quelle
1
Es scheint mir ziemlich begrenzt zu sein. Ich verstehe nicht, warum ein Compiler so etwas verhindern müsste. Es funktioniert gut mit Binden, obwohl die Syntax mit dem ostream Linksschieberoperator schrecklich ist.
Jean-Simon Brochu
3
Könnte tmpsein , ein const &zu gridauf Kopieren zu reduzieren? Wir wollen immer noch mindestens eine Kopie, die Kopie in das Lambda ( [tmp]), aber keine Notwendigkeit für eine zweite Kopie.
Aaron McDaid
4
Die Lösung erstellt möglicherweise eine unnötige zusätzliche Kopie, gridobwohl sie wahrscheinlich optimiert wird. Kürzer und besser ist: auto& tmp = grid;usw.
Tom Swirly
4
Wenn Sie C ++ 14 zur Verfügung haben, können Sie [grid = grid](){ std::cout << grid[0][0] << "\n"; }die zusätzliche Kopie vermeiden
Sigy
Es scheint in gcc 4.9 (und gcc 5.4) error: capture of non-variable ‘puzzle::grid’
behoben zu sein
108

Zusammenfassung der Alternativen:

erfassen this:

auto lambda = [this](){};

Verwenden Sie einen lokalen Verweis auf das Mitglied:

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

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

Trass3r
quelle
5
Interessant, dass hierfür nur die explizite Verwendung des Captures mit Initialisierersyntax funktioniert (dh in C ++ 14 [&grid]funktioniert es immer noch nicht). Sehr froh das zu wissen!
Ohruunuruus
1
Gute Zusammenfassung. Ich finde die C ++ 14-Syntax sehr praktisch
Tuket
22

Ich glaube, du musst erfassen this.

Michael Krelin - Hacker
quelle
1
Dies ist richtig, es wird der this-Zeiger erfasst und Sie können immer noch direkt darauf verweisen grid. Problem ist, was ist, wenn Sie das Raster kopieren möchten? Dies wird Ihnen nicht erlauben, dies zu tun.
Xeo
9
Sie können, aber nur auf Umwegen: Sie müssen eine lokale Kopie erstellen und diese im Lambda erfassen . Dies ist nur die Regel bei Lambdas. Sie können außerhalb des umschließenden Bereichs keine steifen Werte erfassen.
Xeo
Sicher können Sie kopieren. Ich meinte, Sie können es natürlich nicht kopieren und erfassen.
Michael Krelin - Hacker
Was ich beschrieben habe, führt eine Kopienerfassung über die lokale Zwischenkopie durch - siehe meine Antwort. Abgesehen davon kenne ich keine Möglichkeit, die Erfassung einer Mitgliedsvariablen zu kopieren.
Xeo
Sicher, es kopiert die Erfassung, aber nicht das Mitglied. Es handelt sich um zwei Kopien, es sei denn, der Compiler ist schlauer als gewöhnlich, würde ich vermuten.
Michael Krelin - Hacker
14

Eine alternative Methode, die den Umfang des Lambda einschränkt, anstatt ihm Zugriff auf das Ganze thiszu gewähren, besteht darin, einen lokalen Verweis auf die Mitgliedsvariable zu übergeben, z

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
Dlanod
quelle
Ich liebe deine Idee: Verwenden Sie eine gefälschte Referenzvariable und übergeben Sie sie an die Erfassungsliste :)
Emadpres