C++11 lambdat: [](){} Matti Rintala
bool(*)(int) Tarve Tarve välittää kirjastolle/funktiolle toiminnallisuutta Callback-funktiot Virhekäsittely Käyttöliittymät Geneeristen kirjastojen räätälöinti STL:n algoritmit (sort, find_if,...) Koodin rinnakkainen suoritus Aiempi tapa funktio-osoittimilla ja C++03:n (kömpelöillä) funktio-olioilla
bool(*)(int) Funktio-osoittimien rajoitukset Funktio-osoittimet ovat "funktionaalisia" Parametrit ainoa data sisään, viiteparametrit+paluuarvo ulos void alle5(int a) { return a < 5; } std::find_if(v.begin(), v.end(), &alle5); Muun datan välitys funktioon/-sta vaikeaa (lähinnä globaalit muuttujat) Korostuu esim. callback-funktioissa int raja; // Oltava globaali muuttuja! void allerajan(int a) { return a < raja; } std::cin >> raja; std::find_if(v.begin(), v.end(), &allerajan);
(lambda (x) (x x)) Lambdat Lambdat ikivanha keksintö (Lisp) C++11:n lambdoissa kuitenkin joitain eroja Idea: Lambdat ovat funktion kaltaisia tuotteita, ottavat parametreja, palauttavat paluuarvon nimettömiä, määrittelemättömän tyyppisiä (melkein) pystyvät viittaamaan luontiympäristönsä muuttujiin, samoin muuttamaan pystyvät kopioimaan itseensä osia luontiympäristöstään
[](){} Syntaksi Lambdojen syntaksi C++11:ssa [ympäristö](parametrit)->paluutyyppi { runko; } ympäristö tyhjä, jos ei viittaa ympäristöönsä parametrit tyhjä, jos ei parametreja (myös () voi puuttua) jos ->paluutyyppi puuttuu, se on void, paitsi jos runko pelkkä return-lause, jolloin päätellään std::find_if(v.begin(), v.end(), [](int a){return a<5;} ); int raja; // Paikallinen muuttuja std::cin >> raja; std:find_if(v.begin(), v.end(), [raja](int a){return a<raja;} );
[](){} Ympäristöön viittaaminen Lambda voi viitata paikallisiin muuttujiin: lambdan elinaika vs muuttujan elinaika! C++:n ongelma, muuttujilla määrätty elinaika (C++:n muistimalli) Toinen tapa ajatella: lambda saa parametreja ympäristöstään luontihetkellä, normaalit parametrit kutsuhetkellä Kaksi tapaa: arvon ja viitteen välitys
[=](){} Ympäristön kopioiminen Tapa 1: ympäristön kopioiminen = (oletus) Käytetyt muuttujat kopioidaan lambdan sisään (vrt. arvoparametri), elinkaaret eivät ongelma Kopioita voi muuttaa vain, jos lambda on mutable int raja; std::cin >> raja; std::find_if(v.begin(), v.end(), [=raja](int a){return a<raja;} ); std::for_each(v.begin(), v.end(), [raja](int a) mutable { std::cout << + +raja; } );
[&](){} Ympäristöön viittaaminen Tapa 2: Ympäristöön viittaaminen & Lambda käyttää suoraan ympäristön muuttujia Ohjelmoija vastaa, että muuttujat pysyvät elossa int summa = 0; std::for_each(v.begin(), v.end(), [&summa](int a){summa += a;} );
[=a,&](){} Ympäristöön viittaaminen Ympäristöön viittauksia voi yhdistellä int raja = 5; int vertailuja = 0; std::find_if(v.begin(), v.end(), [=raja,&vertailuja](int a)->bool { ++vertailuja; return a<raja; } ); Implisiittinen ympäristöön viittaus (valittava, onko viite vai kopio) int raja = 5; int summa = 0; std::find_if(v.begin(), v.end(), [=](int a){return a<raja;} ); std::for_each(v.begin(), v.end(), [&](int a){summa+=a;} );
[this](){} Ympäristöön viittaaminen Jäsenfunktiossa määritelty lambda voi viitata jäsenmuuttujiin ja -funktioihin class X { int i_; void g(int x); void f(std::vector<int> const& v) { std::for_each(v.begin(), v.end(), [this](int a){ i_+=a; g(a); } ); } };
decltype([](){}) Lambdojen tyyppi Lambda sisältää dataa, ei voi olla normaali funktio Lambdan tyyppi määrittelemätön Lambdan ottavan funktion oltava käytännössä template, jossa parametrityyppi avoin Jos lambda ei viittaa ympäristöönsä, siitä on tyyppimuunnos normaaliin funktio-osoittimeen (välitys vanhaan koodiin) Lambdan välitys ei-templatelle: std::function<>
sizeof([](){}) Sisäinen toteutus Yksi mahdollisuus: lambdat funktio-olioita Nimettömiä luokkia, joissa kutsuoperaattori operator() Jäsenmuuttuja jokaista ympäristöviittausta kohti (viite, jos viittaus ympäristöön) Kääntäjä saa myös toteuttaa miten haluaa Esim. funktio-olio, jossa suoraan aktivaatiotietueen osoite yms.
auto f =[](){}; Esimerkkejä STL:n käyttö std::transform(std::istream_iterator<int>(cin), std::istream_iterator<int>(), std::ostream_iterator<int>(cout,'\n'), [](int a){ return a*a; } ); int i=0; std::generate_n(back_inserter(v), 10, [&](){ return ++i; } ); Rinnakkaisuus std::future<int> t = std::async([](){...}); std::thread t([](){...});
auto f =[](){}; Esimerkkejä Callback-funktiot // Funktio-osoitin, ympäristöön ei viitata on_event(type, void(*)()); on_event(left_click, []{cout<<"painettu";} ); template<typename T> on_event(type, T); on_event(left_click, [&]{nappi=true;} ); on_event(type, function<int()>); on_event(left_click, [&]{nappi=true;} );
auto f =[](){}; Esimerkkejä Paikalliset funktiot void f(std::vector<int>& v, std::list<int>& l) { int x=1; auto kerro = [&](int i){x *= i;}; for (auto i: v) { kerro(i); } for (auto i: l) { kerro(i); } }
auto f =[](){}; Esimerkkejä Nimetyt silmukat for, while eivät kerro, miksi silmukka suoritetaan STL:n algoritmeja voi ajatella nimettyinä erityissilmukoina, joilla lambda runkona Vertaa: bool kaikki = true; for (auto i : v) { if (i%10!= 0) { kaikki = false; break; } } if (kaikki) {...} if (std::all_of(v.begin(), v.end(), [](int i){return i%10==0;}){...}
[a](b){c;} Tulevaisuus Polymorfinen lambda Lambda, parametrien tyyppejä ei määrätty Parametrityypit määräytyvät kutsuttaessa Vrt. template-funktio Toteutus funktio-oliona, jonka operator() template Syntaksi vielä auki (seuraava C++-päivitys?) std::vector<int> vi; std::vector<float> vf; float summa = 0.0; auto summaa = [&](a){summa+=a;}; std::for_each(vi.begin(), vi.end(), summaa); std::for_each(vf.begin(), vf.end(), summaa);