Skip to content

Commit 05112e7

Browse files
committed
Final animations until type erasure
1 parent d93ee6e commit 05112e7

2 files changed

Lines changed: 61 additions & 37 deletions

File tree

animation/projects/src/std_function/scenes/std_function.tsx

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,26 @@ export default makeScene2D(function* (view) {
2929
yield* waitFor(1);
3030

3131
// 1. Template Button
32-
yield* centerOn(codeRef(), lines(4, 5), 1, 40); // template <typename Callback>
32+
yield* centerOn(codeRef(), lines(3, 4), 1, 40); // template <typename Callback>
3333
yield* waitFor(3);
3434

35-
yield* centerOn(codeRef(), lines(17, 17), 1, 40); // Callback on_click_;
35+
yield* centerOn(codeRef(), [lines(16, 16), lines(6, 7)], 1, 40); // Callback on_click_;
3636
yield* waitFor(3);
3737

38-
yield* centerOn(codeRef(), DEFAULT, 1, 28);
39-
yield* waitFor(2);
38+
yield* centerOn(codeRef(), [lines(16, 16), lines(6, 7), lines(9, 12)], 1, 40); // Callback on_click_;
39+
yield* waitFor(3);
40+
41+
yield* centerOn(codeRef(), [lines(24, 25)], 1, 40); // std::vector<Button> buttons;
42+
yield* waitFor(3);
43+
44+
yield* centerOn(codeRef(), [lines(24, 24), lines(22, 22)], 1, 40); // std::vector<Button> buttons;
45+
yield* waitFor(3);
46+
47+
yield* centerOn(codeRef(), [lines(25, 25), lines(19, 19)], 1, 40); // std::vector<Button> buttons;
48+
yield* waitFor(3);
49+
50+
yield* centerOn(codeRef(), [lines(27, 28)], 1, 40); // std::vector<Button> buttons;
51+
yield* waitFor(3);
4052

4153
// 3. std::function Button
4254
yield* codeRef().code(templateCode, 0);
@@ -45,27 +57,33 @@ export default makeScene2D(function* (view) {
4557
yield* codeRef().code(stdFunctionCode, 1);
4658
yield* waitFor(1);
4759

48-
yield* centerOn(codeRef(), lines(8, 9), 1, 40); // explicit Button(std::string name, std::function<void()> callback)
49-
yield* waitFor(3);
60+
yield* centerOn(codeRef(), lines(7, 7), 1, 40); // explicit Button(std::string name, std::function<void()> callback)
61+
yield* waitFor(1);
5062

51-
yield* centerOn(codeRef(), lines(18, 18), 1, 40); // std::function<void()> on_click_;
63+
yield* centerOn(codeRef(), [lines(7, 8), lines(17, 17)], 1, 40); // explicit Button(std::string name, std::function<void()> callback)
5264
yield* waitFor(3);
5365

54-
yield* centerOn(codeRef(), lines(30, 30), 1, 40); // std::vector<Button> buttons{play_button, quit_button};
66+
yield* centerOn(codeRef(), lines(22, 48), 1, 40); // std::function<void()> on_click_;
5567
yield* waitFor(3);
5668

57-
yield* centerOn(codeRef(), DEFAULT, 1, 28);
58-
yield* waitFor(2);
69+
yield* centerOn(codeRef(), lines(28, 30), 1, 40); // std::vector<Button> buttons{play_button, quit_button};
70+
yield* waitFor(3);
5971

6072
// 4. MultiButton
73+
yield* codeRef().code(stdFunctionCode, 0);
74+
yield* centerOn(codeRef(), DEFAULT, 0, 22);
75+
yield* waitFor(1);
6176
yield* codeRef().code(multiButtonCode, 1);
6277
yield* waitFor(1);
6378

64-
yield* centerOn(codeRef(), lines(23, 23), 1, 40); // std::vector<std::function<void()>> on_click_callbacks_;
79+
yield* centerOn(codeRef(), [lines(7, 8), lines(25, 28), lines(30, 30)], 1, 22);
6580
yield* waitFor(3);
6681

67-
yield* centerOn(codeRef(), DEFAULT, 1, 28);
68-
yield* waitFor(2);
82+
yield* centerOn(codeRef(), DEFAULT, 1, 22);
83+
yield* waitFor(3);
84+
85+
yield* centerOn(codeRef(), [lines(13, 13)], 1, 35);
86+
yield* waitFor(3);
6987

7088
// 5. Type Erasure
7189
yield* codeRef().code(typeErasureCode, 1);

lectures/std_function.md

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
- [Type Erasure (How it works under the hood)](#type-erasure-how-it-works-under-the-hood)
99
- [Summary](#summary)
1010

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

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!
1416

1517
<!-- Intro -->
1618

1719
## 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.
1921

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:
2123

2224
<!--
2325
`CPP_COPY_SNIPPET` std_function/template_button.cpp
@@ -56,14 +58,16 @@ int main() {
5658
}
5759
```
5860
59-
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.
6064
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.
6266
6367
However, as soon as we try to build a more complex and dynamic system, things get more complicated.
6468
6569
**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!
6771
6872
<!--
6973
`CPP_SKIP_SNIPPET`
@@ -87,10 +91,10 @@ std::vector<Button<???>> buttons{play_button, quit_button};
8791
**Problem 2: Multiple Mixed Callbacks**
8892
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.
8993

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?
9195

9296
## 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.
9498

9599
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:
96100
<!--
@@ -100,6 +104,8 @@ The syntax for `std::function` is straightforward, it is a template class templa
100104
std::function<ReturnType(ArgumentType1, ArgumentType2, ...)>
101105
```
102106

107+
All of these can be references, pointers, `const` etc. just like any normal function arguments.
108+
103109
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:
104110

105111
<!--
@@ -142,7 +148,9 @@ int main() {
142148
}
143149
```
144150
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.
146154
147155
When we run this, it works beautifully, calling both of our buttons' callbacks:
148156
@@ -165,13 +173,10 @@ And what about our second problem? Now that our callbacks are wrapped in a unifo
165173
#include <string>
166174
#include <vector>
167175

168-
class MultiButton {
176+
class Button {
169177
public:
170-
explicit MultiButton(std::string name) : name_{std::move(name)} {}
171-
172-
void AddCallback(std::function<void()> callback) {
173-
on_click_callbacks_.push_back(std::move(callback));
174-
}
178+
explicit Button(std::string name, std::vector<std::function<void()>> callbacks)
179+
: name_{std::move(name)}, on_click_callbacks_{std::move(callbacks)} {}
175180

176181
void Click() const {
177182
std::cout << "[" << name_ << "] was clicked.\n";
@@ -182,27 +187,28 @@ class MultiButton {
182187

183188
private:
184189
std::string name_;
185-
// ✅ We can store a list of ANY callables!
186190
std::vector<std::function<void()>> on_click_callbacks_;
187191
};
188192

189193
void QuitGame() { std::cout << "Quitting game...\n"; }
190194

191195
int main() {
192-
MultiButton play_button{"Play"};
193-
play_button.AddCallback([] { std::cout << "Playing game!\n"; });
194-
play_button.AddCallback([] { std::cout << "Logging: Play was clicked.\n"; });
196+
Button play_button{"Play", {
197+
[] { std::cout << "Playing game!\n"; },
198+
[] { std::cout << "Logging: Play was clicked.\n"; },
199+
}};
195200

196-
MultiButton quit_button{"Quit"};
197-
quit_button.AddCallback(QuitGame);
201+
Button quit_button{"Quit", {QuitGame}};
198202

199-
std::vector<MultiButton> buttons{play_button, quit_button};
203+
std::vector<Button> buttons{play_button, quit_button};
200204

201205
for (const auto& btn : buttons) { btn.Click(); }
202206
}
203207
```
204208
205-
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:
206212
207213
```
208214
[Play] was clicked.

0 commit comments

Comments
 (0)