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

5 minutos de lectura

avatar de usuario de vivek
Vivek

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

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
  1. ¿Qué compilador es el correcto?
  2. ¿Cómo puedo usar miembros de datos dentro de una lambda en VS2010?

  • Nota: debe 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

    25/10/2011 a las 21:21

  • Relacionado; muy útil: thispointer.com/…

    – Gabriel grapas

    28 de abril de 2020 a las 5:10


  • usar [&] capturar por referencia.

    – herr_azad

    26 de noviembre de 2021 a las 23:16

Avatar de usuario de Xeo
Xeo

Creo que VS2010 es correcto esta vez, y verificaría si tuviera el estándar a mano, pero actualmente no lo tengo.

Ahora, es exactamente como dice el mensaje de error: no se pueden capturar cosas fuera del alcance de la lambda. grid no está en el ámbito de aplicación, pero this es (cada acceso a grid en realidad sucede como this->grid en funciones miembro). Para su caso de uso, capturar this funciona, ya que lo usará de inmediato y no desea copiar el grid

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

Sin embargo, si desea almacenar la cuadrícula y copiarla para acceder a ella posteriormente, donde puzzle Es posible que el objeto ya esté destruido, deberá hacer una copia local intermedia:

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

† Estoy simplificando: busque en Google “alcance del alcance” o vea §5.1.2 para todos los detalles sangrientos.

  • 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 a la izquierda ostream.

    – Jean-Simon Brochu

    27 de noviembre de 2013 a las 14:53

  • Podría tmp ser un const & a grid reducir las copias? Todavía queremos al menos una copia, la copia en la lambda ([tmp]), pero sin necesidad de una segunda copia.

    – Aaron McDaid

    28 de julio de 2015 a las 15:05


  • La solución podría hacer una copia extra innecesaria de grid aunque probablemente se optimice. Más corto y mejor es: auto& tmp = grid; etc.

    – Tom Swirly

    10 de agosto de 2015 a las 17:49

  • Si tiene C++ 14 disponible, podría hacerlo [grid = grid](){ std::cout << grid[0][0] << "\n"; } para evitar la copia extra

    – sigi

    18 de marzo de 2016 a las 11:49


  • Parece estar arreglado en gcc 4.9 (y gcc 5.4 para el caso) error: capture of non-variable ‘puzzle::grid’

    – BGabor

    29 de noviembre de 2019 a las 14:27


Avatar de usuario de Trass3r
Trass3r

Resumen de las alternativas:

captura 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

  • 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 haciendo [&grid] todavía no funciona). ¡Muy contento de saber esto!

    – plata mate

    10 de febrero de 2017 a las 15:53

  • Buen resumen. Encuentro la sintaxis de C++14 muy conveniente

    – tuket

    22 de julio de 2018 a las 12:39

  • Tenga en cuenta que el nombre de la variable puede ser cualquier cosa: auto lambda = [&anyName = grid](){};

    – estrellado

    16 de marzo a las 6:05

Creo que necesitas capturar this.

  • Esto es correcto, capturará el puntero this y aún puede consultar grid directamente. El problema es, ¿qué pasa si quieres copiar la cuadrícula? Esto no te permitirá hacer eso.

    – Xeo

    25/10/2011 a las 21:19

  • Puede, pero solo de forma indirecta: debe hacer una copia local y capturar eso en la lambda. Esa es solo la regla con las lambdas, no puede capturar rígido fuera del alcance adjunto.

    – Xeo

    25/10/2011 a las 21:30

  • Seguro que puedes copiar. Quise decir que no puedes copiarlo y capturarlo, por supuesto.

    – Michael Krelin – hacker

    25/10/2011 a las 21:40

  • 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

    25/10/2011 a las 21:42


  • Claro, copia la captura, pero no el miembro. Se trata de dos copias a menos que el compilador sea más inteligente de lo habitual, supongo.

    – Michael Krelin – hacker

    25/10/2011 a las 21:52

Un método alternativo que limita el alcance de la lambda en lugar de darle acceso a todo this es 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;
   });

¿Ha sido útil esta solución?