Skip to content
Draft
Changes from all 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
276 changes: 276 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,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()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consistency: Braces are same line, single space between () and {. Please fix across the entire document.

{
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';
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consistency: }; should not be indented



// 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.

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
[](){
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 inputs of the lambda are
specified.

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

return 0;
}
```

As is visible above have noticed that the lambda uses the same variable name inside the lambda as declared in main.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Reword second sentence to say "Now, multiply_number is being taken by reference instead of by copy."

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)

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

return 0;
}
```