-
Notifications
You must be signed in to change notification settings - Fork 18
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 all 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,276 @@ | ||
| # Lambdas | ||
|
|
||
| ## What is a lambda? | ||
|
|
||
| Lambdas are similar to functions; however, they are not identical. Specifically, they are | ||
| [closures](<https://en.wikipedia.org/wiki/Closure_(computer_programming)>). | ||
|
|
||
| In the following example a lambda is shown with a matching function. | ||
|
|
||
| ```cpp | ||
| #include <iostream> | ||
|
|
||
| void say_a() | ||
|
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. Consistency: Braces are same line, single space between |
||
| { | ||
| std::cout << "a" << "\n"; | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| auto say_b = [](){ | ||
| std::cout << "b" << "\n"; | ||
| }; | ||
|
|
||
| say_a(); // prints a | ||
| say_b(); // prints b | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| As is visible from the above code snippet a lot of the syntax of functions is shared with lambdas. Where lambdas shine | ||
| is when used in algorithms. In the standard library there are algorithms that can have their behavior changed using | ||
| something callable. Creating a whole function is often excessive, and we use lambdas instead: | ||
|
|
||
| ```cpp | ||
| // A lambda that checks if the passed in letter is not equal to a. | ||
| auto a_checker = [](char c) { | ||
| return c != 'a'; | ||
| }; | ||
|
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. Consistency: |
||
|
|
||
|
|
||
| // the function count_if is used together with the lambda a_checker to get the count of all letters that are not a. | ||
| int non_a_amount = std::ranges::count_if("abcaabbac", a_checker);` | ||
| ``` | ||
|
|
||
| This code sample counts all cases 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 is a lambda object, called print | ||
| auto print = [](const int& n) { std::cout << n << '\n'; }; | ||
|
|
||
| // Here this lambda object in used 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, it can also be called 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 explicitly specified, unlike functions. The return type is deduced by | ||
| the compiler. However trailing return type may be added to the lambda explicitly. | ||
|
|
||
| ```cpp | ||
| // This lambda return an interger value | ||
| []() -> int { | ||
| return 5; | ||
| }; | ||
|
|
||
| // equivalent to the following lambda | ||
| [](){ | ||
|
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 inputs of the lambda are | ||
| specified. | ||
|
|
||
| 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. The body contains all the code that will be executed when | ||
| the lambda is invoked. | ||
|
|
||
| ### Capture list | ||
|
|
||
| As the name implies, the capture list allows values to be captured as the lambda is constructed. Unlike the parameters | ||
| where we specify which values that are passed in when the lambda is invoked. This tends to be particularly common when | ||
| external state is needed in a generic algorithm. | ||
|
|
||
| Take for example a simple program where every number is multiplied 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 a lambda object is declared. The lambda capture the variable multiply_number so it can be used in the lambda. The lambda return the value of the passed in value with multiply_number. | ||
| auto func = [multiply_number](const int& n) { | ||
| return n * multiply_number; | ||
| }; | ||
|
|
||
| // Here the lambda object in used 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; | ||
| } | ||
| ``` | ||
|
|
||
| As is visible above have noticed that the lambda uses the same variable name inside the lambda as declared in main. | ||
|
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. Reword this sentence please. It does not flow well. |
||
| These are actually not the same variables. The multiply_number in the lambda is a copy of the original in main. The | ||
| example can be modified 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 a copy is made of multiply_number called num. The type does not need to be specified here. | ||
|
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. Suggested Reword: Here, a copy of multiply_number is made and assigned to num. Notice that the type is not specified here. |
||
|
|
||
| Making a copy of a value might be undesirable and thus it's also possible to capture the variable as a reference. Now no | ||
|
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. Reword second sentence to say "Now, |
||
| 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) |
||
| This syntax can be mixed 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 `a` is captured by value, `b` as a reference and `c` as a copy into the variable `d` in the lambda. | ||
|
|
||
| As is visible the capture list here is getting rather long. Luckily there are some special values that can help keep the | ||
| lambda small. `=` Can be used captures all used variables in the lambda by value, and `&` 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; | ||
| }; | ||
| ``` | ||
|
|
||
| This can also be mixed with the syntax learned above to capture specifically by reference or value. So for example by | ||
| default a copies can be made except the specific value that is captured 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`. This keyword might sound familiar with this keyword if you | ||
| have worked with classes before. For lambdas however the meaning is slightly different. By specifying this into the | ||
| lambda capture list the lambda is given access to the class variables as if it where part of the parent class itself. | ||
| That also means even private variables can be accessed. | ||
|
|
||
| ```cpp | ||
| #include <iostream> | ||
|
|
||
| class Foo | ||
| { | ||
| private: | ||
| int a = 5; | ||
|
|
||
| public: | ||
| void func(){ | ||
| auto lambda = [this](){ | ||
| // notice how the lambda can access the private member a here! | ||
| std::cout << a << "\n"; | ||
| }; | ||
|
|
||
| lambda(); | ||
| } | ||
| }; | ||
|
|
||
| int main(){ | ||
| Foo obj; | ||
| obj.func(); | ||
|
|
||
| return 0; | ||
| } | ||
| ``` | ||
|
|
||
| Additional variables can be captured 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 the lambda 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.