Uso de la variable miembro en la lista de captura lambda dentro de una función miembro

145

El siguiente código se compila con gcc 4.5.1 pero no 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;
}

Este es el error:

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

Entonces,

1> qué compilador es el correcto?

2> ¿Cómo puedo usar variables miembro dentro de un lambda en VS2010?

vivek
fuente
1
Nota: Debería ser pair<const int, set<int> >, ese es el tipo de par real de un mapa. Posiblemente también debería ser una referencia a const.
Xeo
Relacionado; muy útil: thispointer.com/…
Gabriel Staples

Respuestas:

157

Creo que VS2010 tiene razón esta vez, y comprobaría si tenía el estándar a mano, pero actualmente no lo tengo.

Ahora, es exactamente como dice el mensaje de error: no puede capturar cosas fuera del alcance de la lambda. grid no está en el alcance adjunto, pero sí this(cada acceso a gridrealmente sucede como this->griden las funciones miembro). Para su caso de uso, la captura thisfunciona, ya que la usará de inmediato y no desea copiar elgrid

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

Sin embargo, si desea almacenar la cuadrícula y copiarla para un acceso posterior, donde su puzzleobjeto ya podría estar destruido, deberá hacer una copia local intermedia:

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

† Estoy simplificando: Google para "alcanzar el alcance" o ver §5.1.2 para todos los detalles sangrientos.

Xeo
fuente
1
Me parece bastante limitado. No entiendo por qué un compilador necesitaría evitar tal cosa. Funciona bien con bind, aunque la sintaxis es horrible con el operador de desplazamiento izquierdo ostream.
Jean-Simon Brochu
3
¿Podría tmpser un const &para gridreducir la copia? Todavía queremos al menos una copia, la copia en lambda ( [tmp]), pero no necesitamos una segunda copia.
Aaron McDaid
44
La solución podría hacer una copia adicional innecesaria, gridaunque probablemente se optimice. Más corto y mejor es: auto& tmp = grid;etc.
Tom Swirly
44
Si tiene C ++ 14 disponible, puede hacer [grid = grid](){ std::cout << grid[0][0] << "\n"; }para evitar la copia extra
sigy
Parece estar arreglado en gcc 4.9 (y gcc 5.4 para el caso)error: capture of non-variable ‘puzzle::grid’
BGabor
108

Resumen de las alternativas:

capturar this:

auto lambda = [this](){};

use una referencia local al miembro:

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

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

Trass3r
fuente
55
Es interesante que solo el uso explícito de la captura con la sintaxis del inicializador funcione para esto (es decir, en C ++ 14 simplemente [&grid]no funciona). Muy contento de saber esto!
ohruunuruus
1
Buen resumen Encuentro el 14 ++ sintaxis de C muy conveniente
tuket
22

Creo que necesitas capturar this.

Michael Krelin - hacker
fuente
1
Esto es correcto, capturará el puntero de este y todavía puede referirse griddirectamente. El problema es, ¿qué pasa si quieres copiar la cuadrícula? Esto no te permitirá hacer eso.
Xeo
9
Puede, pero sólo de una manera indirecta: Hay que hacer una copia local, y la captura que en el lambda. Esa es solo la regla con lambdas, no se puede capturar rígido fuera del alcance.
Xeo
Claro que puedes copiar. Quise decir que no puedes copiarlo, capturarlo, por supuesto.
Michael Krelin - pirata informático
Lo que describí hace una captura de copia, a través de la copia local intermedia - vea mi respuesta. Aparte de eso, no conozco ninguna forma de copiar capturar una variable miembro.
Xeo
Claro, copia la captura, pero no el miembro. Supongo que implica dos copias a menos que el compilador sea más inteligente de lo habitual.
Michael Krelin - pirata informático
14

Un método alternativo que limita el alcance de la lambda en lugar de darle acceso al conjunto thises pasar una referencia local a la variable miembro, por ejemplo

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
fuente
Me encanta tu idea: usar una variable de referencia falsa y pasarla a la lista de captura :)
Emadpres