std::optional

Overview
The standard library of C++ includes std::optional<T> class from the C++17 version. std::optional
What is C++ Optional?
A method for handling the null data condition is provided by std::optional<T> wrapper in C++. It frees the user from the work to manage different indicator values like (0 for false, and 1 for true). When a std::optional<T> instance is implicitly converted to a bool type, it returns true if the instance has a value and false otherwise.
Type security and flexibility are provided by the optional types, which is a creation of the functional programming environment. Similar concepts exist in the majority of other languages, such as the Optional
Before the C++17 version, boost::optional has been around for optional usage, but since the C++17 version release std::optional has been introduced with a lot of features to offer, and you can just use it by just including #include<optional> statement in your C++ program.
"std::optional" belongs to the C++ vocabulary types just like "std::variant", "std::string", "std::any" classes.
Commonly Used Member Types of C++ Optional
Member Functions
- optional() constructor: It is used to construct an optional instance without any value. It is a public method by default.
- optional() destructor: It is used to delete the instance value if present. It is also a public method by default.
- operator=: It is used to assign contents to the optional instance. It is also a public method by default.
Observers:
- operator-> or operator"*": These operators can be used to access an instance value of the optional type.
- bool() or has_value(): These methods return true if the instance contains a value, and false otherwise.
- value(): It returns the reference to the instance value, else throws a std::bad_optional_access exception.
- value_or(): It returns the stored instance value if present, else returns a default_value (if not available/valid).
Monadic Operations:
The below methods were introduced in the C++23 version.
- and_then(): It returns the outcome of the specified function on the instance value if available, else it returns an empty optional.
- transform(): It returns an optional with a modified contained value, if an instance value is available, else it returns an empty optional.
- or_else(): It returns an optional, if an instance value is available, or returns the value from the provided function.
Modifiers:
- swap: It is used to interchange the values of optional instances.
- reset: It is used to delete the current instance value.
- emplace: It is used to construct the optional class instance value in-place
Non-Member Functions
- ==,!=,<, <=, >, >=, <=>: These operators are used to compare the optional instance values.
- make_optional(): This method is used to create an instance of the optional class.
- std::swap: It overloads the std::swap mechanism and swaps the left value with the right value, interpreted as left.swap(right).
Helper Classes
- std::hash<std::optional>: It overloads the std::hash mechanism in the optional class.
- nullopt_t: It is used to indicate the uninitialized optional instance.
- bad_optional_access: It is used to define the exception that should be thrown if the optional class instance value is not available.
Helpers
- std::nullopt: It is a constant of nullopt_t type and it is used to denote the non-instantiated objects.
- in_place, in_place_type, in_place_index, in_place_t, in_place_type_t, in_place_index_t: These helpers are supplied to specify the object to be constructed and should be built in-place.
When to Use std::optional?
Generally, the following situations allow you to apply a std::optional wrapper in a C++ program:
-
We can use it for a nullable type to be represented adequately:
- For instance, the middle name of a person is optional in nature. Although it is possible that an empty string "" would suffice in this situation, it may be crucial sometimes to know whether or not a user supplied any data or not. Here, std::optional<std::string> can provide more details about the optional data. So, instead of utilizing singular values such as nullptr, NO_VALUE, -1, "", or something similar we can use std::optional wrapper.
-
We can use it for return values that failed at calculation but weren't an error.
- For instance, when looking up an item in the dictionary, if there isn't the item listed we are looking for, it should not throw an error, we can deal with it using std::optional wrapper.
-
We can use it to implement resource lazy-loading.
- For instance, if a resource type lacks a default constructor function but requires a construction. As a result, you can declare it as std::optional<Resource>, and then load it only when necessary in the C++ program.
-
We can also use it to create functions with optional parameters.
Basic Operations on std::optional
Creating a std::optional
To create a std::optional instance, we can use one of the following syntaxes:
Note: If we include using namespace std; statement in our C++ program, then we can use optional keyword without std namespace. However, it is not recommended to use using namespace std; statement in a C++ program.
In the above code, we can see that the optional class allows you a create an instance of its type with a lot of options. For primitive types, it is extremely easy, and even complex types can be created with ease.
Returning std::optional
Using the return type as std::optional, it becomes highly practical to just return the std::nullopt or the calculated value from the function.
The below code block will also work, it is a bit long but good for understanding the optional class working.
In the above code, we can see that if the string is a valid string it will be returned, else std::nullopt will be returned by the function.
Accessing the Stored Value
Apart from creation, fetching the stored value is likely the most important activity for an optional class instance. Let's look at the ways in which we can access a stored value from the optional instance:
- operator-> or operator*: These operators can be used to access an instance value of the optional type.
- value(): It returns the reference to the instance value, else throws a std::bad_optional_access exception.
- value_or(): It returns the stored instance value if present, else returns a default_value (if not available/valid).
Changing the Value
If you already have an optional instance, changing its stored value is simple. We can utilize methods like reset, swap, and emplace. If you assign it with a nullopt, the destructor will be called. Let's look at the below C++ program to understand the swap() method functioning:
Output:
Explanation: In the above C++ program, simply two strings of type optional class have been exchanged using the swap() method present in the optional class.
Comparisons
std::optional lets you compare its instance almost naturally, only some exceptions occur when dealing with std::nullopt. Let's look at a C++ program to get an understanding of various comparison methods present for std::optional type objects.
Output:
Explanation: In the above C++ program, we are just comparing different instances of the std::optional type and simultaneously printing the result in the output.
Basic Example
Let's first look at an example without using the std::optional wrapper in our C++ program. We are just checking if a value is -1 (nullable type) or it contains a not nullable value in the below program.
Output:
In the above C++ program, we are just checking if the returned value from the getVal() function is -1 (null type) or not. If it is not -1 then we are just printing the val, else nothing.
The std::optional class makes it easier to find if the condition is a success or not. Let's look at the above example using the std::optional class in the below C++ program.
Output:
Explanation: As we know, a std::optional type object contains a flag with itself, now if a value is returned and is not std::nullopt then the flag will be true, else it will be false (we don't have to check whether it is not equal to -1). Basically, we don't have to deal with nullable types like "", -1, nullptr, etc. by using the std::optional class objects in a C++ program.
More Examples of std::optional
Here are a couple of more extensive C++ program examples, where std::optional works well.
User Name with an Optional Nickname and Age
Output:
Explanation: In the above C++ program, we have created a class UserData with three private variables, one optional<string> class variable, one optional<int> class variable, and one normal string type variable. In the main() function, we are making two instances of the UserData class, both instances call the UserData constructor to initialize the private variables. Next, when we print the objects using the << operator, it uses the operator overloading to use the friend function created in the program to print the object variables.
Parsing ints from the Command Line
CLI compilation and passing arguments (1):
Output (1):
CLI compilation and passing arguments (2):
Output (2):
Explanation: In the above C++ program, we have created a convertToInt function which takes the char type item as an argument and converts it to an int using the stoi() method. We are using a try-catch block to catch any exceptions during the conversion. In Output (1), we can see that 1 and 2 are valid integers so we print their sum. In Output (2), we can see that a is not a valid integer so an exception is caught by the catch block and outputs a message invalid char a, cannot convert it to int type!.
Performance & Memory Considerations
We have to incur an additional memory space if we use the std::optional class. We require at least one additional byte. The standard library optional we use in implementation can be theoretically represented as:
Briefly stated, the std::optional class just encapsulates our variable, creates a space for it, and provides one additional boolean flag. Therefore, the std::optional class increases the size of our variable which impacts the performance of the C++ program.
Drawbacks of using std::optional
These are some of the drawbacks of using std::optional in a C++ program:
- std::optional class takes an extra memory space (at least 1 byte) for storing the boolean flag with the stored value.
- Using std::optional may decrease the performance of the C++ program as it takes more memory as compared to simple logic to check the boolean values.
- When we use std::optional with some types like bool or pointer type the result becomes vague and can lead to an ambiguous C++ program.
Special Case: optional<bool> and optional<T*>
Although the std::optional class wrapper can be applied to any type of variable, it is important to take extra care when attempting to wrap pointers or booleans.
std::optional<bool> optionalBool What does this statement imply? It is basically a 3-state bool. In such case, it might be preferable to search for a 3-state bool type like boost::tribool, we use it if it is necessarily required in our C++ program. Furthermore, the optionalBool changes to a bool type which already is a bool. So, utilizing such a type can create some unexpected errors or exceptions in our C++ program.
If we wrap pointers in the std::optional class, we may experience a similar level of confusion. As we know, pointers are inherently nullable, and wrapping them in an optional class makes it very challenging to use these types of objects in a C++ program.
Migration from boost::optional
std::optional class was directly derived from the boost::optional class, both provide similar level of functionalities. Moving from one class to the other is simple, but there are always small variations in usage. Most of the feature comparisons of boost::optional and std::optional are listed in the below table.
| Feature | boost::optional | std::optional |
|---|---|---|
| hash support | no | yes |
| noexcept | yes (latest boost) | yes |
| in_place() | yes, using emplace() and tags in_place_init_if_t, in_place_init_t | yes, using emplace and tag in_place |
| optional references | yes | no |
| Move class semantics | yes(latest boost) | yes |
| throw value | yes | yes |
| literal value | no | yes |
| ptr explicit conversion | yes | no |
| deduction guides | no | yes |
| convert optional<U> to other optional type optional<T> | yes | yes |
| nullable types | yes (none) | yes (nullopt) |
Conclusion
- The standard library of C++ includes std::optional<T> class from the C++17 version and it serves as a wrapper for your type and contains a boolean flag to check if the value has been initialized or not.
- std::optional is a method for handling the nullable type data in C++ programs.
- We can use std::optional class for return values that failed at calculation but weren’t an error. We can use it to implement resource lazy-loading. We can also use it to create functions with optional parameters.
- We use operator*, value(), etc. methods to access the optional instance value, make_optional() to create an instance, swap(), emplace(), and reset() methods to modify the instance value.
- std::optional just encapsulates our variable, creates a space for it, and provides one additional boolean flag. It increases the size of our variable which impacts the performance of the C++ program.
- std::optional class was directly derived from the boost::optional class from C++17 version.