Skip to content

Commit 30338fd

Browse files
committed
Update optional text
1 parent ddeccac commit 30338fd

File tree

1 file changed

+52
-50
lines changed

1 file changed

+52
-50
lines changed

lectures/optional_and_variant.md

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,77 @@
66
<a href="https://youtu.be/dummy_link"><img src="https://img.youtube.com/vi/dummy_link/maxresdefault.jpg" alt="Video Thumbnail" align="right" width=50% style="margin: 0.5rem"></a>
77
</p>
88

9-
## **Introduction**
9+
When working with modern C++ (C++17 and beyond), we often need tools to handle optional values or represent data that can take one of several types. That’s where `std::optional` and `std::variant` come into play. Today, we’ll explore what these features are, why they’re useful, and how to use properly them.
1010

11-
When working with modern C++ (C++17 and beyond), we often need tools to handle optional values or represent data that can take one of several types. That’s where `std::optional` and `std::variant` come into play. Today, we’ll explore what these features are, why they’re useful, and how you can leverage them in your projects.
11+
<!-- Intro -->
1212

13+
## Why use `std::optional`?
14+
To understand why we need `std::optional` I believe its best to start with an example.
1315

14-
## **What is `std::optional`?**
16+
Let's say we have a function `GetAnswerFromLlm` that, getting a question, is supposed to answer all of our questions using some large language model.
17+
```cpp
18+
#include <string>
1519

16-
### Why use `std::optional`?
20+
std::string GetAnswerFromLlm(const std::string& question);
21+
```
1722
18-
Imagine a function that searches for an item in a container. If the item is found, the function should return it. But what if it isn’t? Before C++17, you might have returned a special value (like `-1` for integers) or used a pointer, potentially introducing ambiguity or risking undefined behavior.
23+
In a normal case, this is a good-enough interface, we ask it things and get some answers. But what happens if something goes wrong within this function? What if it _cannot_ answer our question? What should it return so that we know that an error has occurred.
1924
20-
`std::optional` solves this by explicitly representing the absence of a value. It's a type-safe mechanism that avoids the pitfalls of ad-hoc solutions.
25+
Largely speaking there are two school of thought here:
26+
- It can throw an **exceptions** to indicate that some error has happened
27+
- Or it can return a special value to indicate a failure
2128
22-
### Examples of `std::optional` in action
29+
I will not talk too much about exceptions today, I will just mention that in many codebases, especially those that contain safety-critical code, exceptions are banned altogether due to the fact that there is, strictly speaking, no way to guarantee their runtime performance because of their dynamic implementation.
2330
24-
#### A simple search function
31+
This prompted people to think our of the box to avoid using exceptions but still to know that something went wrong during the execution of their function.
2532
26-
````cpp
33+
In the olden days (before C++17), people would return a special value from the function. For example, we could just return some pre-defined string, for example an empty one, should something have gone wrong. But what if we ask our LLM to actually return an empty string and it would fail to do so? What should it return then?
34+
35+
This is where `std::optional` comes to the rescue. We can now return a `std::optional<std::string>` instead of just returning a `std::string`:
36+
```cpp
2737
#include <optional>
28-
#include <iostream>
29-
#include <vector>
38+
#include <string>
3039
31-
std::optional<int> Find(const std::vector<int>& data, int value) {
32-
for (int element : data) {
33-
if (element == value) {
34-
return element; // Return the value if found
35-
}
36-
}
37-
return std::nullopt; // Explicitly indicate "no value"
38-
}
40+
std::optional<std::string> GetAnswerFromLlm(const std::string& question);
41+
```
42+
Now it is super clear when reading this function that it might fail because it only optionally returns a string.
3943

40-
int main() {
41-
std::vector<int> numbers = {1, 2, 3, 4, 5};
42-
auto result = Find(numbers, 3);
43-
44-
if (result) { // Check if a value exists
45-
std::cout << "Found: " << *result << '\n';
46-
} else {
47-
std::cout << "Not found.\n";
48-
}
49-
}
50-
````
51-
In this example, `std::optional<int>` clearly communicates that the function may or may not return a value.
44+
`llm.hpp`
45+
```cpp
46+
#include <optional>
47+
#include <string>
5248

53-
#### A factory function
49+
std::optional<std::string> GetAnswerFromLlm(const std::string& question);
50+
```
5451
55-
````cpp
56-
std::optional<std::string> CreateString(bool should_create) {
57-
if (should_create) {
58-
return "Hello, World!";
59-
}
60-
return std::nullopt;
61-
}
52+
So let's see how we could work with such a function! For this we'll call it a couple of times with various prompts and process the results that we're getting:
6253
63-
int main() {
64-
auto maybe_string = CreateString(true);
54+
`main.cpp`
55+
```cpp
56+
#include "llm.hpp"
6557
66-
if (maybe_string) {
67-
std::cout << *maybe_string << '\n';
68-
} else {
69-
std::cout << "No string created.\n";
70-
}
58+
int main() {
59+
const auto suggestion = GetAnswerFromLlm(
60+
"In one word, what should I do with my life?");
61+
if (!suggestion) return 1;
62+
const auto further_suggestion = GetAnswerFromLlm(
63+
std::string{"In one word, what should I do after doing this: "} + suggestion.value());
64+
if (!further_suggestion.has_value()) return 1;
65+
std::cout <<
66+
"The LLM told me to " << *suggestion <<
67+
", and then to " << *further_suggestion << std::endl;
68+
return 0;
7169
}
72-
````
73-
---
70+
```
71+
In general, `std::optional` provides an interface in which we are able to:
72+
- Check if it holds a value by calling its `has_value()` method or implicitly converting it to `bool`
73+
- Get the stored value by calling `value()` or using a dereferencing operator `*`. Beware, though that getting a value of an optional that holds no value is undefined behavior, so _always check_ that there is actually a value stored in an optional.
74+
75+
There are many use-cases for `optional` in situations where we want to be able to handle a case where a value might exist but also might be missing under certain circumstances.
7476

75-
## **What is `std::variant`?**
77+
<!-- TODO: talk about how it is implemented through variant and maybe std expected, also get_value_or -->
7678

77-
### Why use `std::variant`?
79+
## Why use `std::variant`?
7880

7981
`std::variant` is a type-safe union introduced in C++17. It allows a variable to hold one value out of a defined set of types. Think of it as a more flexible alternative to `enum` or `std::any`, but with static type checking.
8082

0 commit comments

Comments
 (0)