Використання змінної члена у списку захоплення лямбда всередині функції члена


145

Наступний код компілюється з gcc 4.5.1, але не з 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;
}

Це помилка:

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> який компілятор правильний?

2> Як я можу використовувати змінні члена всередині лямбда у VS2010?


1
Примітка. Має бути pair<const int, set<int> >, це фактичний тип пари на карті. Можливо, це також має бути посиланням на const.
Xeo

Споріднені; дуже корисно: thispointer.com/…
Габріель Степлес

Відповіді:


157

Я вважаю, що VS2010 цього разу є правильним, і я би перевірив, чи був у мене стандарт під рукою, але наразі цього не роблю.

Тепер це точно так, як йдеться у повідомленні про помилку: Ви не можете захоплювати речі поза межами рамки лямбда. grid не входить в область, що додається, але thisє (кожен доступ gridнасправді відбувається, як this->gridу функціях членів). Для вашої корисної скриньки thisпрацює захоплення , оскільки ви будете використовувати його відразу, і ви не хочете копіюватиgrid

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

Якщо ви хочете зберегти сітку та скопіювати її для подальшого доступу, де ваш puzzleоб’єкт вже може бути знищений, вам потрібно зробити проміжну локальну копію:

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

† Я спрощую - Google для "досягнення обсягу" або див. Пункт 5.1.2 для всіх деталей горіхів.


1
Мені це здається досить обмеженим. Я не розумію, чому компілятору потрібно було б запобігти подібному. Він добре працює з прив'язкою, хоча синтаксис жахливий з оператором зсуву лівого потоку.
Жан-Саймон Броху

3
Може tmpбути , const &щоб gridскоротити копіювання? Ми все ще хочемо хоча б одну копію, копію в лямбда ( [tmp]), але немає потреби в другій копії.
Аарон Макдейд

4
Рішення може зробити зайву додаткову копію, gridхоча воно, ймовірно, оптимізується. Коротше і краще: auto& tmp = grid;пр.
Том Свірлі

4
Якщо у вас є C ++ 14, ви можете зробити це, [grid = grid](){ std::cout << grid[0][0] << "\n"; }щоб уникнути зайвої копії
підпишіть

Здається, це зафіксовано в gcc 4.9 (і gcc 5.4 з цього приводу)error: capture of non-variable ‘puzzle::grid’
BGabor

108

Короткий зміст альтернативних варіантів:

захоплення this:

auto lambda = [this](){};

використовувати локальну посилання на учасника:

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

приклад: https://godbolt.org/g/dEKVGD


5
Цікаво, що для цього працює лише явне використання захоплення з синтаксисом ініціалізатора (тобто в C ++ 14 просто все [&grid]ще не працює). Дуже рада це знати!
ohruunuruus

1
Гарне резюме. Я вважаю синтаксис C ++ 14 дуже зручним
tuket

22

Я вважаю, що вам потрібно захопити this.


1
Це правильно, він захопить цей покажчик, і ви все ще можете просто посилатися gridбезпосередньо. Проблема в тому, що, якщо ви хочете скопіювати сітку? Це не дозволить вам це зробити.
Xeo

9
Можна, але тільки обхідним шляхом: Ви повинні зробити локальну копію, і захоплення , що в лямбда. Це лише правило з лямбдами, ви не можете зафіксувати жорсткість поза рамками, що додаються.
Ксео

Звичайно, ви можете скопіювати. Я мав на увазі, що ви, звичайно, не можете його скопіювати.
Майкл Крелін - хакер

Те, що я описав, робить захоплення копії через проміжну локальну копію - дивіться мою відповідь. Крім цього, я не знаю жодного способу скопіювати захоплення змінної члена.
Xeo

Звичайно, це робить захоплення копії, але не член. Він включає дві копії, якщо компілятор не розумніший, ніж зазвичай, я б здогадався.
Майкл Крелін - хакер

14

Альтернативний метод, який обмежує сферу дії лямбда, а не надає їй доступ до цілого, this- це передати локальну посилання на змінну члена, наприклад

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

Мені подобається ваша ідея: використовуйте підроблену змінну посилання та передайте її до списку захоплення :)
Emadpres
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.