Nel contesto della definizione delle espressioni lambda, il qualificatore mutable assume una valenza semantica particolare.
L'uso del qualificare mutable
in questo contesto rende infatti possibile alterare il valore delle variabili catturate per valore nel corpo di istruzioni di una espressione lambda.
La sintassi è mostrata nella figura seguente, in cui il qualificatore, se presente, viene collocato subito dopo la lista di argomenti della espressione.
La cattura per valore, come discusso in precedenza, consente di catturare per copia una variabile dal contesto esterno alla espressione. Per default, tale copia è accessibile all'interno del corpo di istruzioni in sola lettura, ma non è modificabile.
Ciò consente di invocare la medesima espressione lambda più volte, senza il rischio di alterare i valori catturati in origine dal contesto esterno tra un esecuzione e l'altra.
La cattura per riferimento, invece, consente di alterare liberamente le variabili catturate dall'esterno. Come conseguenza di ciò, a seguito dell'esecuzione dell'espressione lambda, il contesto di invocazione risulta alterato.
Un esempio di questo comportamento è riportato nel listato seguente:
#include <iostream>
int main()
{
int num1 = 0;
auto lambda_Capture_By_Ref = [&num1] () -> int {
// L'alterazione di num1 è possibile per effetto della cattura per riferimento.
// Catturando num1 per valore, l'istruzione num1++ darebbe luogo ad un errore di compilazione.
return num1++;
};
std::cout << "Lambdas with capture by reference" << std::endl;
for (int i=0; i<5; i++)
std::cout << lambda_Capture_By_Ref() << "\n";
// Il valore di num1 nel contesto di origine è cambiato.
std::cout << "Num1: " << num1 << std::endl;
return 0;
}
Il quale produce il seguente output:
Lambdas with capture by reference
0
1
2
3
4
Num1: 5
A seconda dei casi, l'alterazione del contesto esterno può essere un effetto collaterale innocuo oppure potenzialmente pericoloso per la stabilità del programma.
In questi casi, l'uso del qualificatore mutable
consente di catturare per valore variabili esterne e di modificare liberamente il valore della copia catturata nel corpo di istruzioni. In pratica, le alterazioni prodotte dal corpo della espressione lambda persistono tra un'esecuzione e l'altra, ma non hanno effetti nel contesto esterno.
Nel listato seguente, il corpo di istruzioni della lambda risulta inalterato rispetto l'esempio precedente.
#include <iostream>
int main()
{
int num1 = 0;
// mutable lambda
auto mutable_lambda_Capture_By_Value = [num1] () mutable -> int {
// L'alterazione di num1 è possibile per effetto del qualificatore mutable
return num1++;
};
std::cout << "Mutable lambdas with capture by value" << std::endl;
for (int i=0; i<5; i++)
{
// Il valore della copia di num1 catturata dalla lambda, è incrementato ad ogni iterazione.
std::cout << mutable_lambda_Capture_By_Value() << "\n";
}
// Ma il valore di num1 non è cambiato.
std::cout << "Num1: " << num1 << std::endl;
return 0;
}
In effetti, analizzando l'output del programma, le molteplici invocazioni dell'espressione producono la medesima sequenza di numeri. Tuttavia, a differenza del caso precedente, l'uso della cattura per valore unitamente al qualificatore mutable
lasciano immutato il contesto esterno.
Mutable lambdas with capture by value
0
1
2
3
4
Num1: 0
La variabile num1 originale non è infatti soggetta a nessuna alterazione, visto che è stata catturata per copia.
Con l'aggiunta di questo componente, la sintassi delle espressioni lambda consente quindi di particolarizzare l'uso della modalità di cattura per valore al fine di implementare una sorta di gestione dello stato interno della lambda, che non ha ripercussioni sull'ambiente esterno ad essa.
Si ricordi che, in effetti, l'uso di espressioni lambda è sostanzialmente una sintassi concisa per implementare dei funtori. In questo senso, le variabili catturate sono come membri privati di classe usati per immagazzinare copie o riferimenti a variabili esterne.
In termini non rigorosi, possiamo quindi equiparare le variabili catturate per copia a membri di classe decorati per default con il qualificatore const.
Il qualificatore mutable
applicato alla definizione di un'espressione lambda consente di cambiare questo comportamento predefinito.
In pratica, decorando una lambda con mutable
stiamo indicando in maniera esplicita che il suo stato, inteso come insieme delle variabili catturate per valore dall'esterno, può evolversi per effetto delle istruzioni presenti nel corpo della lambda, senza tuttavia produrre effetti collaterali nel mondo esterno.