Usando variável de membro na lista de captura lambda dentro de uma função de membro

145

O código a seguir é compilado com o gcc 4.5.1, mas não com o 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 é o erro:

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

Assim,

1> qual compilador está certo?

2> Como posso usar variáveis ​​de membro dentro de um lambda no VS2010?

vivek
fonte
1
Nota: Deveria ser pair<const int, set<int> >, esse é o tipo de par real de um mapa. Possivelmente também deve ser uma referência a const.
Xeo 25/10/11
Relacionado; muito útil: thispointer.com/…
Gabriel Staples

Respostas:

157

Acredito que o VS2010 esteja certo neste momento e verificaria se tinha o padrão à mão, mas atualmente não tenho.

Agora, é exatamente como a mensagem de erro diz: Você não pode capturar coisas fora do escopo do lambda. grid não está no escopo anexo, mas sim this(todo acesso a gridrealmente acontece como this->gridem funções-membro). Para seu caso de usuário, capturar thistrabalhos, pois você o usará imediatamente e não deseja copiar ogrid

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

Se, no entanto, você deseja armazenar a grade e copiá-la para acesso posterior, onde seu puzzleobjeto já pode estar destruído, será necessário fazer uma cópia local intermediária:

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

† Estou simplificando - o Google por "alcançar o escopo" ou consulte §5.1.2 para todos os detalhes sangrentos.

Xeo
fonte
1
Parece bastante limitado para mim. Não entendo por que um compilador precisaria impedir isso. Funciona bem com o bind, embora a sintaxe seja horrível com o operador ostream left shift.
precisa
3
Poderia tmpser uma forma const &de gridreduzir a cópia? Ainda queremos pelo menos uma cópia, a cópia no lambda ( [tmp]), mas não precisamos de uma segunda cópia.
Aaron McDaid
4
A solução pode fazer uma cópia extra desnecessária, gridembora provavelmente seja otimizada. Mais curto e melhor é: auto& tmp = grid;etc.
Tom Swirly
4
Se você tiver C ++ 14 disponíveis, você poderia fazer [grid = grid](){ std::cout << grid[0][0] << "\n"; }para evitar a cópia extra
sigy
Parece estar corrigido no gcc 4.9 (e no gcc 5.4)error: capture of non-variable ‘puzzle::grid’
BGabor 29/11/19
108

Resumo das alternativas:

captura this:

auto lambda = [this](){};

use uma referência local para o 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

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

Trass3r
fonte
5
Interessante que apenas o uso explícito da captura com sintaxe do inicializador funcione para isso (ou seja, no C ++ 14, apenas fazer [&grid]ainda não funciona). Muito feliz em saber isso!
ohruunuruus
1
Bom resumo. Acho a sintaxe do C ++ 14 muito conveniente #
tuket
22

Eu acredito que você precisa capturar this.

Michael Krelin - hacker
fonte
1
Isso está correto, ele irá capturar o ponteiro this e você ainda pode se referir griddiretamente. Problema sendo, e se você quiser copiar a grade? Isso não permitirá que você faça isso.
Xeo
9
Você pode, mas apenas de uma forma indireta: Você tem que fazer uma cópia local e captura que no lambda. Essa é apenas a regra com lambdas, você não pode capturar rígido fora do escopo.
Xeo 25/10/11
Claro que você pode copiar. Eu quis dizer que você não pode copiá-lo, é claro.
Michael Krelin - hacker
O que eu descrevi faz uma captura de cópia, através da cópia local intermediária - veja minha resposta. Além disso, não conheço nenhuma maneira de copiar capturar uma variável de membro.
Xeo 25/10/11
Claro, ele copia a captura, mas não o membro. Envolve duas cópias, a menos que o compilador seja mais inteligente que o normal, eu acho.
Michael Krelin - hacker de
14

Um método alternativo que limita o escopo do lambda em vez de fornecer acesso ao todo thisé passar uma referência local à variável de membro, por exemplo,

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
fonte
Eu amo a sua ideia: usar uma variável de referência falso e passá-lo à lista de captura :)
Emadpres