Skip to content
Draft
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions src/resources/cpp/lambdas.md
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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
[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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should leave discussion on "when to use lambdas" to after they have been discussed more thoroughly. I also don't like the voice of "why would I ever need a lambda?". I would rather see a direct discussion of when to use lambdas versus when to use functions without "I" or "you" pronouns.

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'.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest focusing more on the lambda first, then bringing in things like ranges/algorithms. Here, you're introducing two new concepts: lambdas and a range algorithm. I would just provide a name lambda, discuss what the lambda is, then pass it to the range algo and describe how the two interact.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this code sample a bit and introduced a simpler code example first. @nickclark2016 is this better for you?


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.

for a lambda the return type does not need to be specified like functions do. The return type is deduced by the
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
[](){
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reword: It holds the code code that the lambda will execute when it is invoked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body contains all the code that will be executed when the lambda is invoked.

Is that better?

invoked.

### Capture list

As the name implies, the capture list allows us to capture values into the lambda as it's constructed. Unlike the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweak on wording: "capture value into the lambda where it is defined". Constructed feels a bit weird in the context of a lambda.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the name implies, the capture list allows us to capture values as the lambda is constructed.

Hopefully that's better.

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.
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);

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;
};
```

Copy link
Collaborator

Choose a reason for hiding this comment

The 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(){
Foo obj;
obj.func();

return 0;
}
```