-
Notifications
You must be signed in to change notification settings - Fork 15
Introduction to lambdas #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
bdd0a5e
50dda00
064d04c
419cd21
406527d
6358257
2521400
b954807
81e9252
7f7e9d1
b1eac3a
1d7bd66
c2fd62d
384595c
55e3b75
f6be794
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check spelling, capitalization, and contractions across the whole file. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| # Lambdas | ||
|
|
||
| ## What is a lambda? | ||
|
|
||
| Lambdas are similar to functions, however they are not identical they specifically are | ||
mcmlevi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| [closures](<https://en.wikipedia.org/wiki/Closure_(computer_programming)>). | ||
|
|
||
| if they are similar to functions one might ask why would I ever need a lambda? For example, algorithms in the standard | ||
|
||
| library can often have their behavior changed using something callable. Creating a whole function is often excessive, | ||
| and we use lambdas instead: | ||
|
|
||
| `int non_a_amount = std::ranges::count_if("abcaabbac", [](char c) { return c != 'a'; });` | ||
|
|
||
| This counts all results where the lambda returns true, in this case if the latter is not 'a'. | ||
|
||
|
|
||
| Another example is where we want to print each value in an array: | ||
|
|
||
| ```cpp | ||
| #include <algorithm> | ||
| #include <iostream> | ||
| #include <vector> | ||
|
|
||
| int main() | ||
| { | ||
| std::vector<int> v{3, -4, 2, -8, 15, 267}; | ||
|
|
||
| // Here we declare a local lambda object, called print | ||
| auto print = [](const int& n) { std::cout << n << '\n'; }; | ||
|
|
||
| // Here we use this lambda object in the algorithm for each, where the lambda will be called for each | ||
| // iteration and print the value in the vector. | ||
| std::for_each(v.cbegin(), v.cend(), print); | ||
|
|
||
| // As a lambda is similar to a function we can also call it like a function. This will print 5 | ||
| print(5); | ||
|
|
||
| return 0; | ||
| } | ||
| ``` | ||
|
|
||
| ## Structure of lambdas | ||
|
|
||
| A lambda consists of at least three parts: | ||
|
|
||
| - `[]`, the introducer, containing captures. | ||
| - `()`, the parameters (optional in some cases). | ||
| - `{}`, the body of the lambda. | ||
|
|
||
| There are many further optional parts, but `[](){}` or `[]{}` are the bare minimum, depending on the language version. | ||
mcmlevi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| for a lambda the return type does not need to be specified like functions do. The return type is deduced by the | ||
mcmlevi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| compiler. However you can specify a trailing return type to the lambda. | ||
|
|
||
| ```cpp | ||
| // This lambda return an interger value | ||
| []() -> int { | ||
| return 5; | ||
| }; | ||
|
|
||
| // ``equivalent to the following lambda | ||
mcmlevi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| [](){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would tweak the spacing such that your lambdas are: []() {
};This is to match the function style specification |
||
| return 5; | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ### Parameters `()` | ||
|
|
||
| The parameter part of a lambda is the exact same as the parameter list of a function. Here you specify the inputs of | ||
| your lambda. | ||
|
|
||
| For example `[](int a, int b){}` is a lambda that takes in 2 integer values. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would use a full block, rather than inline. |
||
|
|
||
| ### body `{}` | ||
|
|
||
| The lambda body is the exact same as the body of the function. It's hold the code that the lambda will execute when it's | ||
|
||
| invoked. | ||
|
|
||
| ### Capture list | ||
|
|
||
| As the name implies, the capture list allows us to capture values into the lambda as it's constructed. Unlike the | ||
|
||
| parameters where we specify which values we want to pass in when we invoke the lambda. This tends to be particularly | ||
| common when you want to bring external state into a generic algorithm. | ||
|
|
||
| Take for example a simple program where you want to multiply every number with a number the user provides. | ||
|
|
||
| ```cpp | ||
| #include <algorithm> | ||
| #include <iostream> | ||
| #include <vector> | ||
|
|
||
| int main() | ||
| { | ||
| std::vector<int> v{3, -4, 2, -8, 15, 267}; | ||
|
|
||
| std::cout << "Please enter a number to multiply with"; | ||
| int multiply_number = 0; | ||
| std::cin >> multiply_number; | ||
|
|
||
| // Here we declare a local lambda object. We capture the variable multiply_number so it can be used in the lambda. We return the value of the passed in value with multiply_number. | ||
mcmlevi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| auto func = [multiply_number](const int& n) { | ||
| return n * multiply_number; | ||
| }; | ||
|
|
||
| // Here we use this lambda object in the algorithm transform. This algorithm takes a ranges to iterate over, an output destination (the begin of the same vector in this case) and the predicate (our lambda) | ||
| std::transform(v.cbegin(), v.cend(), v.begin(), func); | ||
mcmlevi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return 0; | ||
| } | ||
| ``` | ||
|
|
||
| You might have noticed that the lambda uses the same variable name inside the lambda as declared in main. These are | ||
| actually not the same variables. The multiply_number in the lambda is a copy of the original in main. you can modify the | ||
| above example like this to give the variable a different name if so desired. | ||
|
|
||
| ```cpp | ||
| auto func = [num = multiply_number](const int& n) { | ||
| return n * num; | ||
| }; | ||
| ``` | ||
|
|
||
| Here we make a copy of multiply_number called num, we do not need to specify the type here. | ||
|
|
||
| Making a copy of a value might be undesirable and if wanted we can also capture the variable as a reference. Now no copy | ||
| is made of multiply_number. | ||
|
|
||
| ```cpp | ||
| auto func = [&multiply_number](const int& n) { | ||
| return n * multiply_number; | ||
| }; | ||
| ``` | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a question (no action needed necessarily) - Should we talk about capture by reference and note how people need to be careful when using capture by reference/be aware of the lifetime implications (i.e. your lambda may have an invalid reference if the original object has its lifetime end before the lambda's lifetime ends) |
||
| You can mix this syntax to capture multiple variables. | ||
|
|
||
| ```cpp | ||
| int a = 1; | ||
| int b = 2; | ||
| int c = 3; | ||
| auto func = [a, &b, d = c](const int& n) { | ||
| return a + b + d; | ||
| }; | ||
| ``` | ||
|
|
||
| Here we capture `a` by value, `b` as a reference and `c` as a copy into the variable `d` in the lambda. | ||
|
|
||
| As you noticed the list here is getting rather long. Luckily we have some special values that can help us keep the | ||
| lambda small. We have `=` that captures all used variables in the lambda by value. and `&` that captures all variables | ||
| as a reference. | ||
|
|
||
| ```cpp | ||
| int a = 1; | ||
| int b = 2; | ||
| int c = 3; | ||
| int d = 4; | ||
| // a,b, c are copied into the lambda. d is not copied as it's not used. | ||
| auto func = [=](const int& n) { | ||
| return a + b + c; | ||
| }; | ||
|
|
||
| // a,b, c are captured as a reference into the lambda. | ||
| auto func2 = [&](const int& n) { | ||
| return a + b + c; | ||
| }; | ||
| ``` | ||
|
|
||
| You can also mix this with the syntax you learned above to capture specifically by reference or value. So for example by | ||
| default we make a copy except the specific value we capture as a reference. | ||
|
|
||
| ```cpp | ||
| int a = 1; | ||
| int b = 2; | ||
| int c = 3; | ||
| int d = 4; | ||
| // a,b are copied into the lambda. d is not copied as it's not used. and c is captured as a reference into the lambda. | ||
| auto func = [=, &c](const int& n) { | ||
| return a + b + c; | ||
| }; | ||
| ``` | ||
|
|
||
| ## `this` keyword | ||
|
|
||
| The lambda has 1 additional special keyword called `this` you might be familiar with this keyword if you have worked | ||
| with classes. For lambdas however the meaning is slightly different. By specifying this into the lambda capture list you | ||
| give it access to the class variables as if it where part of the parent class itself. even private variables. | ||
|
|
||
| ```cpp | ||
| #include <iostream> | ||
|
|
||
| class Foo | ||
| { | ||
| private: | ||
| int a = 5; | ||
|
|
||
| public: | ||
| void func(){ | ||
| auto lambda = [this](){ | ||
| // notice how we can access the private member a here! | ||
| std::cout << a << "\n"; | ||
| }; | ||
|
|
||
| lambda(); | ||
| } | ||
| }; | ||
|
|
||
| int main(){ | ||
| Foo obj; | ||
| obj.func(); | ||
|
|
||
| return 0; | ||
| } | ||
| ``` | ||
|
|
||
| You can capture additional variables as well with the same rules as described in the previous chapter. | ||
|
|
||
| ```cpp | ||
| #include <iostream> | ||
|
|
||
| class Foo | ||
| { | ||
| private: | ||
| int a = 5; | ||
|
|
||
| public: | ||
| void func(){ | ||
| int b = 1; | ||
| int c = 2; | ||
|
|
||
| // b is captured as a copy, and c is captured by reference. | ||
| auto lambda = [this, b, &c](){ | ||
| // notice how we can access the private member a here! as well as the local variable b and c | ||
| std::cout << a << " " << b << " " << c << "\n"; | ||
| }; | ||
|
|
||
| lambda(); | ||
| } | ||
| }; | ||
|
|
||
| int main(){ | ||
mcmlevi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Foo obj; | ||
| obj.func(); | ||
|
|
||
| return 0; | ||
| } | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General voice comment: Consider moving "we" and "you" to the specific part of the code whenever possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a closer look at this, and wow, do I use this pattern a lot. I looked at the entire file and tried to address the wording a lot to use a more passive style. This should also be more fitting for the intended purpose, I feel.