[ 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