Skip to content

Commit d451807

Browse files
committed
Add more or less complete text about lambdas
1 parent 789987e commit d451807

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

lectures/lambdas.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ Lambdas
55
<a href="https://youtu.be/blah"><img src="https://img.youtube.com/vi/blah/maxresdefault.jpg" alt="Video" align="right" width=50% style="margin: 0.5rem"></a>
66
</p>
77

8+
- [Lambdas](#lambdas)
9+
- [Before lambdas we had functors](#before-lambdas-we-had-functors)
10+
- [How is the sorting function implemented?](#how-is-the-sorting-function-implemented)
11+
- [Enter lambdas](#enter-lambdas)
12+
- [When to use lambdas](#when-to-use-lambdas)
13+
- [Summary](#summary)
14+
15+
816
We've talked about so many things, like classes and functions but there is one more thing that modern C++ has that we did not really touch upon - lambdas.
917

1018
Here's what they are useful for. Imagine we have a vector of people, represented as a struct `Person`, and we would like to sort them by age. We can use the standard [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) function for that.
@@ -80,7 +88,164 @@ int main() {
8088
Print(people);
8189
}
8290
```
91+
Note that we can also drop the `&` such that the call to `std::sort` becomes:
92+
```cpp
93+
std::sort(people.begin(), people.end(), less);
94+
```
95+
The reason for this is that [functions are implicitly converted to function pointers](https://en.cppreference.com/w/cpp/language/implicit_conversion#Function-to-pointer_conversion) if needed, they are special in this way.
8396

8497
But what if this is not enough? What if we need to have a certain state? For example, we wouldn't want to sort the people by their absolute age, but by the difference of their age with respect to some number, say `4242`.
8598

8699
Behold **function objects**, or **functors**. These are objects for which the function call operator is defined, or, in other words, that define an operator `()`.
100+
101+
So, if we want to sort our array by the age difference to `4242` we can create a struct `ComparisonToQueryAge` that has a member `query_age_` and an operator `()` that compares the age differences instead of directly the ages:
102+
```cpp
103+
#include <algorithm>
104+
#include <iostream>
105+
#include <string>
106+
#include <vector>
107+
108+
struct Person {
109+
std::string name;
110+
int age;
111+
};
112+
113+
void Print(const std::vector<Person>& persons) {
114+
for (const auto& person : persons) {
115+
std::cout << person.name << " " << person.age << "\n";
116+
}
117+
}
118+
119+
struct ComparisonToQueryAge {
120+
ComparisonToQueryAge(int query_age) : query_age_{query_age} {}
121+
122+
bool operator()(const Person& p1, const Person& p2) const noexcept {
123+
return std::abs(p1.age - query_age_) < std::abs(p2.age - query_age_);
124+
}
125+
126+
int query_age_{};
127+
};
128+
129+
int main() {
130+
std::vector<Person> people{
131+
{"Gendalf", 55'000}, {"Frodo", 33}, {"Legolas", 2'931}, {"Gimli", 139}};
132+
Print(people);
133+
std::sort(people.begin(), people.end(), ComparisonToQueryAge{4242});
134+
std::cout << "------ sorted --------" << std::endl;
135+
Print(people);
136+
}
137+
```
138+
139+
## How is the sorting function implemented?
140+
So far so good. We already know a lot about structs and classes as well as their methods, so I hope that how these operate seems quite intuitive here. Furthermore, thinking back to the lectures in which we covered templates we can also imagine how to implement a function similar to `std::sort` that would take any object that is "callable" by using templates:
141+
```cpp
142+
template <class Iterator, class Comparator>
143+
void MySort(Iterator begin, Iterator end, Comparator comparator) {
144+
// Sort using comparator(*iter_1, *iter_2) as a building block.
145+
}
146+
147+
int main() {
148+
std::vector<Person> people{
149+
{"Gendalf", 55'000}, {"Frodo", 33}, {"Legolas", 2'931}, {"Gimli", 139}};
150+
MySort(people.begin(), people.end(), ComparisonToQueryAge{4242});
151+
MySort(people.begin(), people.end(), less);
152+
MySort(people.begin(), people.end(), &less);
153+
}
154+
```
155+
156+
And the story doesn't end with `std::sort`. There is a number of functions that take these function objects. For some example, see `std::find_if`, `std::for_each`, `std::transform`, etc.
157+
158+
## Enter lambdas
159+
However, it might not be convenient to always define a new struct, class, or even function for any use case. Sometimes we want to use such a function object only locally, within a function and don't want any overhead.
160+
161+
That convenience is what brought us the lambdas. This is really just a syntactic sugar for defining out own function objects using special syntax.
162+
163+
The syntax of defining a lambda is the following:
164+
```cpp
165+
[const] auto LambdaName = [CAPTURE_LIST](ARGUMENTS){BODY} -> ReturnType;
166+
// We can call it with
167+
LambdaName(ARGUMENTS);
168+
```
169+
So now you see that `[](){}()` is just a definition of a lambda that has an empty capture list, no arguments, empty body, which is called in-place right after creation (doing nothing of course). Totally useless, but a completely valid syntax!
170+
171+
We can replace all of our use-cases with such lambdas:
172+
```cpp
173+
#include <algorithm>
174+
#include <iostream>
175+
#include <string>
176+
#include <vector>
177+
178+
struct Person {
179+
std::string name;
180+
int age;
181+
};
182+
183+
void Print(const std::vector<Person>& persons) {
184+
for (const auto& person : persons) {
185+
std::cout << person.name << " " << person.age << "\n";
186+
}
187+
}
188+
189+
int main() {
190+
std::vector<Person> people{
191+
{"Gendalf", 55'000}, {"Frodo", 33}, {"Legolas", 2'931}, {"Gimli", 139}};
192+
Print(people);
193+
std::sort(people.begin(), people.end(),
194+
[](const auto& p1, const auto& p2) { return p1.age < p2.age; });
195+
std::cout << "------ sorted --------" << std::endl;
196+
Print(people);
197+
198+
int query_age = 4242;
199+
std::sort(people.begin(), people.end(),
200+
[query_age](const auto& p1, const auto& p2) {
201+
return std::abs(p1.age - query_age) <
202+
std::abs(p2.age - query_age);
203+
});
204+
std::cout << "------ sorted --------" << std::endl;
205+
Print(people);
206+
}
207+
```
208+
209+
Furthermore we can store a lambda in a variable and reuse it multiple times. In our example, we can observe that we use `Print` function only in our main function here. While there is no issue with this function being a standalone function in an unnamed namespace, we might as well make it a lambda:
210+
```cpp
211+
#include <algorithm>
212+
#include <iostream>
213+
#include <string>
214+
#include <vector>
215+
216+
struct Person {
217+
std::string name;
218+
int age;
219+
};
220+
221+
int main() {
222+
const auto Print = [](const auto& persons) {
223+
for (const auto& person : persons) {
224+
std::cout << person.name << " " << person.age << "\n";
225+
}
226+
};
227+
228+
std::vector<Person> people{
229+
{"Gendalf", 55'000}, {"Frodo", 33}, {"Legolas", 2'931}, {"Gimli", 139}};
230+
Print(people);
231+
std::sort(people.begin(), people.end(),
232+
[](const auto& p1, const auto& p2) { return p1.age < p2.age; });
233+
std::cout << "------ sorted --------" << std::endl;
234+
Print(people);
235+
236+
int query_age = 4242;
237+
std::sort(people.begin(), people.end(),
238+
[query_age](const auto& p1, const auto& p2) {
239+
return std::abs(p1.age - query_age) <
240+
std::abs(p2.age - query_age);
241+
});
242+
std::cout << "------ sorted --------" << std::endl;
243+
Print(people);
244+
}
245+
```
246+
247+
## When to use lambdas
248+
Lambdas are neat and efficient. If you need an operation that you don't think you'll need to reuse to pass into some other function, like in our example with sorting, lambdas are your friend. Alternatively, if you are implementing some functionality in a header file and find yourself writing a bit of a longer function, lambdas are usually a better way to split such function into meaningful chunks without introducing public-facing functions and not relying on comments that can easily go out of sync with the code functionality.
249+
250+
## Summary
251+
TODO

0 commit comments

Comments
 (0)