You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lectures/std_function.md
+30-24Lines changed: 30 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,16 +8,18 @@
8
8
-[Type Erasure (How it works under the hood)](#type-erasure-how-it-works-under-the-hood)
9
9
-[Summary](#summary)
10
10
11
-
In one of our [previous lectures](lambdas.md) we looked at what lambdas are and how they work. We saw how they give us an easy and clean way to create function objects on the fly, saving us from writing boilerplate structs just to pass, for example, a simple comparator or an operation to a standard algorithm.
11
+
Remember in one of our [previous lectures](lambdas.md) we looked at lambdas? We saw how they give us an easy and clean way to create function objects on the fly, saving us from writing boilerplate structs just to pass a callable - for example, a simple comparator or an operation to a standard algorithm.
12
12
13
-
But what happens when we want to store these callables for later? And what if these callables could be *anything* that behaves like a function? This is exactly where [`std::function`](https://en.cppreference.com/cpp/utility/functional/function) comes to the rescue!
13
+
But what happens if we want to store these callables somewhere and call them at a later time? And what if we want these callables to be *anything* that behaves like a function, not just lambdas?
14
+
15
+
This setup is exactly where [`std::function`](https://en.cppreference.com/cpp/utility/functional/function) comes to the rescue!
14
16
15
17
<!-- Intro -->
16
18
17
19
## The problem: storing callables
18
-
As always, let's start with an illustrative example. Imagine we are building a UI framework, and we want to create a `Button` class. The button doesn't have to know *what* should happen when it is clicked, it just needs to know that it *has* been clicked. The user of our `Button` object should be able to hook up any logic they want by passing in a *callback*: a callable that is called when an action takes place, in our case, when the button gets clicked.
20
+
As always, let's start with an illustrative example. Imagine we are building a UI framework, and we want to create a `Button` class. A button doesn't have to know *what* should happen when it is clicked, it just needs to know that it *has* been clicked. The user of our `Button` object should be able to hook up any logic they want by passing in a *callback* - a callable that is called when an action takes place, in our case, when the button gets clicked.
19
21
20
-
One thing we could do is to use **templates**. If we template the entire `Button` class on the type of the callback, we can store the provided callback as a member variable, say, `on_click` and call it when the click occurs:
22
+
One thing we could do is to use **templates**. If we template the entire `Button` class on the type of the callable, we can store the provided callback as a member variable, say, `on_click_` and call it when the click occurs:
With this approach, we can create, say a "play" button and a "quit" button that each calls their own callback. It's not a bad approach! Look how our templated `Button` handles the different types of callables seamlessly. Here we pass a lambda into the "play" button callback and a free standing function into the "quit" button callback.
61
+
With this approach, we can create, say a "play" button and a "quit" button that each calls their own callback. It's not a bad approach!
62
+
63
+
Look how our templated `Button` handles the different types of callables seamlessly. We don't even need to provide the template type as it is inferred using [Class Template Argument Deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)! Here we pass a lambda into the "play" button callback and a free standing function into the "quit" button callback.
60
64
61
-
If we just want to create buttons and use them in isolation, like in our example, this approach works wonderfully! On top of everything else, it's blazing fast because the compiler knows the exact type of the callback at compile time and can optimize it perfectly.
65
+
If we just want to create these buttons and use them in isolation, like in our example, this approach works wonderfully! On top of everything else, it's blazing fast because the compiler knows the exact type of the callback at compile time and can optimize it perfectly.
62
66
63
67
However, as soon as we try to build a more complex and dynamic system, things get more complicated.
64
68
65
69
**Problem 1: Heterogeneous Collections**
66
-
For example, a UI framework usually needs a way to draw all buttons on the screen or handle their input in a loop. So we would like to put them all in a `std::vector`. But `std::vector` requires all its elements to be of the *exact same type*. Because `play_button` is templated on a lambda (a unique, unnamable class generated by the compiler) and `quit_button` is templated on a function pointer, they are [two entirely different types](https://cppinsights.io/s/edda9741). We cannot store them in the same vector!
70
+
For example, a UI framework usually needs a way to draw all buttons on the screen or handle their input in a loop. So we would like to put them all in a `std::vector`. But `std::vector` requires all its elements to be of the *exact same type*. Because `play_button` is templated on a lambda (a unique class generated by the compiler) and `quit_button` is templated on a function pointer, they are [two entirely different types](https://cppinsights.io/s/edda9741). We cannot store them in the same vector!
In addition to that, what if we wanted a single button to trigger *multiple* actions when clicked? We could change `Callback on_click_` to `std::vector<Callback> on_click_callbacks_`. But again, because `std::vector` demands a single type for all of its elements, we could only store multiple copies of the *exact same* lambda or the *exact same* function pointer. We couldn't mix a lambda and a function object inside the same button's list of callbacks.
89
93
90
-
But what about [`std::variant`](variant.md), I hear some of you ask? Well, technically it would allow us to store various types of callables in a single vector, but some of those types would be horrible temporary anonymous classes generated by the compiler (like in the case of a lambda)! So we wouldn't be able to provide them to our variant upfront! Which means that we would have to turn our code inside out to let compiler infer these types on its own - which is at least hard and probably not even possible in many cases. Surely there must be a better way!
94
+
But what about [`std::variant`](variant.md), I hear some of you ask? Well, technically it would allow us to store various types of callables in a single vector, but some of those types would be horrible temporary anonymous classes generated by the compiler (like in the case of a lambda)! So we wouldn't be able to provide them to our variant upfront! Which brings us to the same problem: what type would a vector store?
91
95
92
96
## Enter `std::function`
93
-
And of course there is one! Otherwise I wouldn't have recorded this video, would I? To solve all of these problems we can use `std::function`, introduced in C++11 and available in the `<functional>` header. It is a wrapper that can store, copy, move, and invoke *any* callable target — be it a function, a lambda expression, a [bind](https://en.cppreference.com/cpp/utility/functional/bind) expression, or a function object — as long as its signature matches the one specified in the `std::function` template argument.
97
+
To solve all of these problems we can use `std::function`, introduced in C++11 and available in the `<functional>` header. It is a wrapper that can store, copy, move, and invoke *any* callable target — be it a function, a lambda expression, a [bind](https://en.cppreference.com/cpp/utility/functional/bind) expression, or a function object — as long as its signature matches the one specified in the `std::function` template argument.
94
98
95
99
The syntax for `std::function` is straightforward, it is a template class templated on the return type as well as all the argument types in parentheses:
96
100
<!--
@@ -100,6 +104,8 @@ The syntax for `std::function` is straightforward, it is a template class templa
All of these can be references, pointers, `const` etc. just like any normal function arguments.
108
+
103
109
Let's rewrite our `Button` class using `std::function` then. We will remove the template from the class and instead use `std::function<void()>` for our callback (meaning "takes no arguments, returns void"). Let's start by keeping exactly one callback per button, matching our original example:
104
110
105
111
<!--
@@ -142,7 +148,9 @@ int main() {
142
148
}
143
149
```
144
150
145
-
There are not that many changes really! The `Button` class is not templated anymore but takes a `std::function<void()>` callback as a parameter and stores this callback internally. The main function also remains very similar, except now we can put our buttons in a vector.
151
+
There are not that many changes really! The `Button` class is not templated anymore but takes a `std::function<void()>` callback as a parameter and stores this callback internally.
152
+
153
+
The main function also remains very similar, except now we can put our buttons in a vector.
146
154
147
155
When we run this, it works beautifully, calling both of our buttons' callbacks:
148
156
@@ -165,13 +173,10 @@ And what about our second problem? Now that our callbacks are wrapped in a unifo
The rest of the example stays largely the same with the main difference being that we have to call `AddCallback` for each callback we want to add to a button. When we run the code, all of our callbacks are being called as expected:
209
+
The rest of the example stays largely the same with the main difference being that we now pass a vector of callbacks to a button.
210
+
211
+
When we run the code, all of our callbacks are being called as expected:
0 commit comments