[ domknięcie ] ( parametry ) -> typ_zwracany { ciało }W nawiasie okrągłym podajemy listę parametrów, jak dla zwykłej funkcji. Po „strzałce” podajemy typ zwracany funkcji: w pewnych sytuacjach można tę część opuścić, a kompilator sam ten typ wydedukuje — tak na przykład będzie jeśli ciało funkcji składa się z jednej instrukcji return; typem zwracanym będzie wtedy decltype zwracanego wyrażenia (o decltype pisaliśmy w rozdziale o typach danych ). Na początku wyrażenia mamy kwadratowe nawiasy, które mogą być puste — oznacza to wtedy, że wszystkie potrzebne dane funkcja otrzymuje poprzez argumenty wywołania. Można jednak umieścić w tych nawiasach, oddzielone przecinkami, symbole:
Typem funkcji lambda jest nieokreślony przez standard typ, być może inny dla każdej lambdy, nawet jeśli takie same są typy argumentów i typ zwracany. Co jednak ważne, typ ten jest konwertowalny do typu function<Typ(Typy)> (z nagłówka functional), gdzie Typ jest typem zwracanym funkcji (być może void), a Typy to typy argumentów oddzielone przecinkami. W wielu, ale nie wszystkich, przypadkach można się posłużyć słowem kluczowym auto aby uniknąć konieczności jawnego definiowania typu. Zwykłe wskaźniki funkcyjne są konwertowane to tego rodzaju typów automatycznie.
1. #include <iostream> 2. #include <functional> 3. using std::cout; using std::endl; 4. 5. double square(double x) { 6. return x*x; 7. } 8. 9. void invoke(std::function<double(double)> f, double arg) { 10. double res = f(arg); 11. cout << "invoke(" << arg << ")=" << res << endl; 12. } 13. 14. int main() { 15. // pomocnicza funkcja lambda 16. auto print = 17. [](double p1, double p2, double p3, 18. double arg, double val) -> void 19. { 20. cout << " a=" << p1 << " b=" << p2 21. << " c=" << p3 << " x=" << arg 22. << " res=" << val << endl; 23. }; 24. 25. // funkcja lambda a*x*x+b*x+c 26. int a = 1, b = 1, c = 1; 27. // lokalne zmienne przez wartość 28. auto pol1 = 29. [=](double x) -> double 30. { 31. double res = c+x*(b+x*a); 32. print(a,b,c,x,res); 33. return res; 34. }; 35. cout << "pol1=" << pol1(2) << endl; 36. a = b = c = 2; 37. cout << "pol1=" << pol1(2) << endl << endl; 38. 39. // lokalne zmienne przez referencje 40. auto pol2 = 41. [&](double x) -> double 42. { 43. double res = c+x*(b+x*a); 44. print(a,b,c,x,res); 45. return res; 46. }; 47. cout << "pol2=" << pol2(2) << endl; 48. a = b = c = 1; 49. cout << "pol2=" << pol2(2) << endl << endl; 50. 51. // a i c przez referencje, b i print przez wartość 52. auto pol3 = ➊ 53. [&a,b,&c,print](double x) -> double 54. { 55. double res = c+x*(b+x*a); 56. print(a,b,c,x,res); 57. return res; 58. }; 59. cout << "pol3=" << pol3(2) << endl; ➋ 60. a = b = c = 2; ➌ 61. cout << "pol3=" << pol3(2) << endl << endl; ➍ 62. 63. // typ określony jawnie 64. std::function<double(double)> f = pol3; 65. invoke(f,2); 66. // konwersja zwykłych wskaźników funkcyjnych 67. invoke(square,2); 68. f = square; 69. invoke(f,2); 70. 71. // dla void->void tylko nawiasy i ciało 72. [] { 73. cout << "Done" << endl; 74. }(); // zdefiniuj funkcję i od razu ją wywołaj 75. }
Funkcja invoke pobiera funkcję lambda (lub wskaźnik do funkcji odpowiedniego typu) i wywołuje przekazaną funkcję dla podanego argumentu. Na początku funkcji main (a więc wewnątrz funkcji) definiujemy pomocniczą funkcję lambda print z pustym domknięciem (cała informacja będzie przekazywana poprzez argumenty). Zauważmy, że print samo jest tu zmienną lokalną. Funkcja ta jest potem wywoływana kilka razy w treści programu. Następnie używając słowa kluczowego auto definiujemy kilka prostych funkcji(pol1, pol2, pol3 — wszystkie są implementacją tego samego wielomianu drugiego stopnia ax2 + bx + c) używając różnych domknięć: jedne zmienne lokalne są przekazywane przez wartość, a więc kopiowane są wartości jakie przyjmują w momencie definiowania funkcji, do innych funkcja będzie miała dostęp przez referencję, a więc będą w niej widoczne zmiany wartości odpowiednich zmiennych. Na przykład, w linii ➊ definiujemy lambdę z domknięciem zawierającym aktualne wartości b (czyli 1) i print oraz referencje do a i c (które również mają wartość 1). Wywołując funkcję z x = 2 (linia ➋), otrzymujemy 7. Następnie zmieniamy wartości zmiennych a, b i c — teraz wszystkie wynoszą 2 (linia ➌). Jednak wartość b widziana przez funkcję w dalszym ciągu wynosi 1, bo zapamiętana została wartość przyjmowana w momencie definiowania lambdy. Z drugiej strony, zmienne a i c widziane są przez referencje, a więc ich zmiany będą widoczne w funkcji i wywołanie z linii ➍ da rezultat 12.
W końcowej części programu demonstrujemy konwersje zwykłych wskaźników funkcyjnych i przekazywanie funkcji lambda i wskaźników funkcyjnych do innych funkcji (w tym przypadku do funkcji invoke). Widać, że wskaźnik jest niejawnie konwertowany do typu std::function<double(double)> (konwersja w drugą stronę nie zachodzi).
Ważne jest przeanalizowanie programu i zrozumienie otrzymanego rezultatu:
a=1 b=1 c=1 x=2 res=7 pol1=7 a=1 b=1 c=1 x=2 res=7 pol1=7 a=2 b=2 c=2 x=2 res=14 pol2=14 a=1 b=1 c=1 x=2 res=7 pol2=7 a=1 b=1 c=1 x=2 res=7 pol3=7 a=2 b=1 c=2 x=2 res=12 pol3=12 a=2 b=1 c=2 x=2 res=12 invoke(2)=12 invoke(2)=4 invoke(2)=4 Done
T.R. Werner, 21 lutego 2016; 20:17