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?


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.


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


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.


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;
   });

Me encanta tu idea: usar una variable de referencia falsa y pasarla a la lista de captura :)
— Emadpres
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.