Nel post precedente ho presentato le lambda function/expression introdotte da C++0x, specificando che di fatto si tratta di funzioni-oggetto.
Proseguiamo nel discorso, cercando di mostrare almeno un caso in cui è comodo impiegarle…
Lamba e Polymorphic Function Wrapper
In questo piccolo codice di esempio ho creato una piccola funzione, nella quale viene definita una lambda.
Tale “funzione” non fa altro che sommare due interi e moltiplicarli per un valore randomico, creato da uno dei nuovi generatori di numeri random introdotti dallo standard (“extensible random number facility“), precisamente un Mersenne twister engine.
La lambda cattura “by reference” il generatore di numeri casuali definito anch’esso nella funzione createClosure(): si tratta quindi di una closure a tutti gli effetti.
La parte piacevole è però il poterla impacchettare (“wrap”) in polymorphic function wrapper (anch’essi un tipo di funzione-oggetto di cui ho già parlato in passato) e quindi ritornarla.
La nuova std::bind
Da notare anche l’utilizzo di std::bind (ripreso dal C++ TR1), che serve a “legare” più parametri ad una funzione o un oggetto.
In questo caso, legando l’“engine Mersenne twister” (parametro) all’oggetto “distribuzione uniforme di interi compresi fra 0 e 99“, si ottiene l’oggetto “generatore di numeri casuali“.
Molto elegante, non trovate?
Propriamente (sempre dall’ultimo draft pubblicato):
“The function template bind <returns an object that binds a function callable object passed as an argument to additional arguments”
L’introduzione di bind, definito come variadic template, rende quindi deprecate le “soluzioni” precedenti:
“D.9 Binders
The binders binder1st, bind1st, binder2nd, and bind2nd are deprecated. [ Note: The function template bind (20.8.11) provides a better solution.—end note ]“
Vediamo dunque un piccolo codice di esempio che mostra questa possibilità (testato anche in questo caso con G++ 4.5 e con Visual C++ 2010)…
Codice di esempio
Nota: nonostante i commenti e l’estrema compattezza del codice, il seguente esempio non è esattamente semplice. Per cui non vi preoccupate se non riuscite a comprenderlo. Consideratelo, se volete, una sorta di una “demo” di alcune funzionalità del nuovo standard C++0x. Grazie.
/**
* FILE : ReturnClosureExample.cpp
* AUTHOR : Gian Paolo "JP" Ghilardi (http://rejex.wordpress.com)
* LICENSE : released under the terms of GPL v2.0 ("only")
* COMPILE : g++ -Wall -Winline -pedantic -std=c++0x
* ReturnClosureExample.cpp -o ReturnClosureExample
* PURPOSE : simple C++0x example showing lambda <-> polymorphic function wrapper usage
* and how to return a closure
*
* TESTED ON :
* - Windows XP SP3, x86 32-bit, G++ 4.5.0
* - Windows XP SP3, x86 32-bit, Visual C++ 2010
*
* REFERENCES:
* [1]: http://en.wikipedia.org/wiki/Lambda_expression
* [2]: http://en.wikipedia.org/wiki/C%2B%2B0x#Lambda_functions_and_expressions
* [3]: http://en.wikipedia.org/wiki/Closure_%28computer_science%29
* [4]: http://gcc.gnu.org/gcc-4.5/cxx0x_status.html
* [5]: http://en.wikipedia.org/wiki/C%2B%2B0x#Alternative_function_syntax
* [6]: http://en.wikipedia.org/wiki/C%2B%2B0x#Polymorphous_wrappers_for_function_objects
* [7]: http://en.wikipedia.org/wiki/C%2B%2B0x#Extensible_random_number_facility
* [8]: http://en.wikipedia.org/wiki/C%2B%2B0x#Type_inference
* [9]: http://en.wikipedia.org/wiki/C%2B%2B_Technical_Report_1#Function_object_binders
*/
#include <cstdlib>
#include <iostream>
#include <functional> // for [6]
#include <random> // for [7]
using namespace std;
// this function will return a "polymorphic function wrapper" [6]
inline function<int(int, int)> returnClosure()
{
uniform_int_distribution<int> distribution(0, 99); // uniform integer distribution
mt19937 engine; // Mersenne twister engine
auto generator = bind(distribution, engine); // binding the objects [8, 9]
// returning a closure: the lambda will be automatically
// "converted" in a "polymorphic function wrapper" [6]!
return
(
// the lambda-closure captures the "generator" object by-reference.
// moreover it uses the alternative function syntax ("-> int") to explicitly
// specify the return type [5]
[&generator](int a, int b) -> int
{
int rndNumber = generator(); // obtaining a new random value
return (b - a) * rndNumber;
}
);
}
int main(int argc, char ** argv)
{
cout << "\nC++0x EXAMPLE:" << endl;
cout << "Simple example showing how to return closures in C++0x\n" << endl;
// storing the returned closure in/as a "polymorphic function wrapper" [6]
function<int(int, int)> pfWrapper = returnClosure(); // [1, 2, 3, 4, 6]
for(int i = 0; i < 5; i++) // each time we call pfWrapper, rndNumber changes...
cout << "Return value: " << pfWrapper(5, 11) << endl; // prints '6 * rndNumber'
return EXIT_SUCCESS;
}
Il codice in esecuzione
P.S.: spero che Contezero apprezzi lo sforzo (mio) e l’eleganza (delle lambda)…

Saluti Paolo,
bell’articolo (come sempre!!!) L’esempio di per se non è troppo complesso se si hanno abbastanza chiari i due concetti principali dell’esempio stesso: le lambda function e il function wrapper. Personalmente avrei “typedeffato” function e magari usato la keyword ‘auto’ nel main ma sono ‘gusti’…
Una cosa non mi è chiara. generator viene passato come ref alla funzione lambda; teoricamente, forse qui che sbaglio, quando la funzione termina la variabile esce dallo scope e viene eliminata dallo stack. In realtà però viene riutilizzata nell’invocazione della funzione lambda successivamente. C’è qualche cosa che mi sfugge, il catturare la variabile implica il passaggio della ownership?…
molte grazie come sempre
ps aggiungo che sono molto felice che sono state deprecate quelle brutte e poco usabili funzioni di binding…avevano troppe limitazioni!
@martino: diciamo che ritorno qualcosa (come se fosse) “per copia”, per cui “the problem is not quite as severe as it seems” (cfr. post).
Comunque ragionando per via teorica, hai perfettamente ragione: volendo potevo/potrei incapsulare tutto in uno shared_ptr(), ma renderebbe tutto più difficile da leggere…
Per chiudere il cerchio, alla fine della fiera l’oggetto-ancora è pfWrapper, che rimane nello scope della main() fino alla fine.
Esso “riceve” la closure – all’uscita della funzione che la produce – (un prvalue temporaneo chiamato “closure type”=> han cambiato ancora la nomenclatura, argh!) che è anch’essa a tutti gli effetti una funzione-oggetto con le sue belle variabili “importate” dal contesto in cui era stata definita (cioè le variabili catturate).
Rimane la questione relativa a “scope e vita (prolungata) della lambda/closure stessa“: ho riletto svariate volte tutta la parte del draft riguardante questo argomento e, da quanto ho liberamente interpretato, una “lambda” può sopravvivere finchè anche l’ultimo contesto che la usa termina (tipo “l’ultimo chiude la porta”, in questo caso la main() stessa).
Dal draft:
Credo che ciò implichi il continuare a mantenere in vita anche lo scope in cui è stata definita (per le variabili catturate!) finchè serve o, come spero, semplicemente “simulare” in qualche modo questo fatto. Il come credo sia al solito platform-dependent…
Ciau e grazie per essere passato! ^^