Capturarea valorilor externe în expresiile lambda
O expresie lambda poate "captura" variabile definite în afara ei, în contextul exterior. Pentru aceasta se folosesc parantezele pătrate care încep expresia.
Capturarea prin valoare
Dacă dorim să capturăm toate variabilele externe din contextul unde este definită lambda prin valoare, în parantezele pătrate scriem simbolul egal =. În acest caz, valorile capturate nu pot fi modificate în interiorul lambda:
#include <iostream>
int main()
{
int n{10};
auto add = [=](int x) { std::cout << x + n << std::endl; };
add(4); // 14
}
Datorită expresiei [=], lambda poate accesa variabila externă n și folosi valoarea acesteia.
Pentru o astfel de lambda, compilatorul va genera o clasă asemănătoare cu:
class __Lambda1c8
{
public:
__Lambda1c8(const int& arg1) : n(arg1) {}
auto operator()(int x) const
{
std::cout << x + n << std::endl;
}
private:
int n;
};
Aici trebuie notate câteva aspecte:
- Valoarea variabilei externe este transmisă printr-un parametru reprezentat prin referință constantă și stocată într-o variabilă privată
- Operatorul () este definit ca const, deci nu putem modifica valoarea variabilei private
- De aceea, variabilele externe capturate prin valoare pot fi folosite, dar nu modificate
Chiar dacă nu putem modifica direct variabila externă, putem captura un pointer la variabila externă prin valoare și modifica valoarea variabilei prin acel pointer în lambda:
#include <iostream>
int main()
{
int n{10};
int* np {&n};
auto increment = [np](){(*np)++;};
increment(); // n = 10
std::cout << "n = " << n << std::endl; // n = 11
}
Capturarea prin referință
Dacă dorim să capturăm variabilele externe prin referință, în parantezele pătrate se scrie simbolul &. În acest caz lambda poate modifica variabilele externe:
#include <iostream>
int main()
{
int n{10};
auto increment = [&]() {
n++; // creștem valoarea variabilei externe n
std::cout << "n inside lambda: " << n << std::endl;
};
increment();
std::cout << "n outside lambda: " << n << std::endl;
}
Datorită expresiei [&], lambda increment poate accesa n prin referință și-i poate modifica valoarea. Astfel, valoarea lui n crește cu 1. Rezultatul pe consolă va fi:
n inside lambda: 11
n outside lambda: 11
Pentru o astfel de lambda, compilatorul generează o clasă asemănătoare:
class __Lambda1c8
{
public:
__Lambda1c8(int& arg1) : n(arg1) {}
auto operator()() const
{
n++;
std::cout << "n inside lambda: " << n << std::endl;
}
private:
int& n;
};
Chiar dacă operatorul () este const la fel ca în exemplul anterior, pentru că variabila capturată este o referință, putem modifica valoarea externă prin această referință.
paremetri-mutable
În cazul anterior, am reușit să obținem o variabilă externă și să îi modificăm valoarea. Dar uneori este necesar să modificăm o copie a variabilei pe care o folosește expresia lambda, nu însăși variabila externă. În acest caz, putem adăuga cuvântul cheie mutable după lista parametrilor:
#include <iostream>
int main()
{
int n{10};
auto increment = [=]() mutable {
n++; // incrementăm valoarea variabilei externe n
std::cout << "n inside lambda: " << n << std::endl;
};
increment();
std::cout << "n outside lambda: " << n << std::endl;
}
Aici, variabila externă n este transmisă prin valoare, deci nu o putem modifica, dar putem modifica copia acestei valori utilizată în interiorul lambda, ceea ce ne va arăta ieșirea la consolă:
n inside lambda: 11
n outside lambda: 10
Obținerea unor variabile specifice
În mod implicit, expresiile [=]/[&] permit capturarea tuturor variabilelor din context. Dar, de asemenea, putem captura doar anumite variabile. Pentru a obține variabile externe, se utilizează expresia [&nume_variabilă]:
#include <iostream>
int main()
{
int n{10};
// obținem variabila externă n prin referință
auto increment = [&n](){ n++;};
increment();
std::cout << "n: " << n << std::endl; // n = 11
}
Dacă trebuie să obținem variabila externă prin valoare, atunci pur și simplu îi indicăm numele în parantezele pătrate:
#include <iostream>
int main()
{
int n{10};
// obținem variabila externă n prin valoare
auto increment = [n](){ std::cout << "n: " << n << std::endl;};
increment(); // n = 10
}
Dacă trebuie să capturăm mai multe variabile, ele se indică prin virgulă. Dacă variabila este transmisă prin referință, înainte de numele său se pune semnul &:
[&k, l, &m, n] // k și m - prin referință, l și n - prin valoare
Putem transmite totul prin valoare și doar anumite variabile prin referință:
[=, &m, &n] // toate prin valoare, iar m și n - prin referință
Sau, dimpotrivă, putem transmite totul prin referință și doar anumite variabile prin valoare:
[&, m, n] // toate prin referință, iar m și n - prin valoare
Capturarea membrilor clasei
Pentru a accesa membrii unei clase – variabile și funcții (indiferent dacă sunt private sau publice) – se folosește expresia [this]:
#include <iostream>
void printer(auto func)
{
std::cout << "*******************"<< std::endl;
func();
std::cout << "*******************"<< std::endl;
}
class Message
{
public:
Message(const std::string& text): text{text}
{}
void print()
{
printer([this](){ std::cout << text << std::endl;});
}
private:
std::string text;
};
int main()
{
Message hello{"Hello World"};
hello.print();
}
În funcția print a clasei Message, afișăm pe consolă textul mesajului stocat în variabila text. Pentru afișare se folosește funcția externă printer, care adaugă un anumit decor și execută funcția transmisă ca parametru. În acest caz, funcția utilizată este o expresie lambda care accesează variabila text a clasei.
Împreună cu pointerul this, putem captura și alte variabile din context. În acest scop, this poate fi combinat cu & sau = și cu capturarea unor variabile specifice. De exemplu: [=, this], [this, &n] și [x, this, &n].