Utilizzo della variabile membro nell'elenco di acquisizione lambda all'interno di una funzione membro


145

Il codice seguente viene compilato con gcc 4.5.1 ma non con 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;
}

Questo è l'errore:

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

Così,

1> quale compilatore è giusto?

2> Come posso usare le variabili membro all'interno di una lambda in VS2010?


1
Nota: dovrebbe essere pair<const int, set<int> >, questo è l'effettivo tipo di coppia di una mappa. Dovrebbe anche essere un riferimento a const.
Xeo,

Relazionato; molto utile: thispointer.com/…
Gabriel Staples il

Risposte:


157

Credo che VS2010 abbia ragione questa volta, e controllerei se avessi lo standard a portata di mano, ma al momento no.

Ora, è esattamente come dice il messaggio di errore: non è possibile catturare oggetti al di fuori dell'ambito racchiuso della lambda. grid non rientra nell'ambito di applicazione, ma lo thisè (ogni accesso gridavviene effettivamente come this->gridnelle funzioni membro). Per il tuo caso d'uso, catturare thisopere, dal momento che lo userai subito e non vuoi copiarlogrid

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

Se tuttavia, desideri archiviare la griglia e copiarla per un accesso successivo, dove il tuo puzzleoggetto potrebbe essere già stato distrutto, dovrai creare una copia locale intermedia:

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

† Sto semplificando - Google per "raggiungere l'ambito" o vedere §5.1.2 per tutti i dettagli cruenti.


1
Mi sembra abbastanza limitato. Non capisco perché un compilatore dovrebbe impedire una cosa del genere. Funziona bene con il bind, sebbene la sintassi sia orribile con l'operatore di spostamento a sinistra ostream.
Jean-Simon Brochu,

3
Potrebbe tmpessere un const &to gridridurre la copia? Vogliamo ancora almeno una copia, la copia in lambda ( [tmp]), ma non è necessaria una seconda copia.
Aaron McDaid,

4
La soluzione potrebbe fare una copia extra superflua gridanche se probabilmente viene ottimizzata. Più breve e migliore è: auto& tmp = grid;ecc.
Tom Swirly,

4
Se hai C ++ 14 disponibile, potresti [grid = grid](){ std::cout << grid[0][0] << "\n"; }evitare la copia aggiuntiva
firma il

Sembra che sia stato corretto in gcc 4.9 (e gcc 5.4 per quella materia)error: capture of non-variable ‘puzzle::grid’
BGabor

108

Riepilogo delle alternative:

cattura this:

auto lambda = [this](){};

utilizzare un riferimento locale al membro:

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

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


5
È interessante notare che solo l'uso esplicito della cattura con la sintassi dell'inizializzatore funziona per questo (vale a dire che in C ++ 14 il solo fare [&grid]non funziona ancora). Molto felice di saperlo!
ohruunuruus,

1
Buon riassunto. Trovo il C ++ 14 sintassi molto conveniente
tuket

22

Credo che tu debba catturare this.


1
Questo è corretto, catturerà questo puntatore e puoi ancora fare riferimento griddirettamente. Problema, cosa succede se si desidera copiare la griglia? Questo non ti permetterà di farlo.
Xeo,

9
È possibile, ma solo in modo indiretto: Devi fare una copia locale, e la cattura che nel lambda. Questa è solo la regola con lambda, non è possibile acquisire rigidi al di fuori dell'ambito di applicazione.
Xeo,

Sicuro che puoi copiare. Volevo dire che non puoi copiarlo, ovviamente.
Michael Krelin - hacker

Quello che ho descritto fa un'acquisizione della copia, attraverso la copia locale intermedia - vedi la mia risposta. A parte questo, non conosco alcun modo per copiare catturare una variabile membro.
Xeo,

Certo, copia l'acquisizione, ma non il membro. Implica due copie a meno che il compilatore non sia più intelligente del solito, immagino.
Michael Krelin - hacker

14

Un metodo alternativo che limita l'ambito del lambda piuttosto che dargli accesso all'intero thisè passare un riferimento locale alla variabile membro, ad es.

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });

Adoro la tua idea: usare una falsa variabile di riferimento e passarla all'elenco di acquisizione :)
Emadpres,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.