std::optional in C++

Cengizhan Varlı
6 min readOct 21, 2023

--

There is a situation that we often encounter in programming. It may be as expected for an object not to have a value as it is for it to have a value. There may be variables or expressions to be used and evaluated as optional in each possible algorithm. For example, assigning or not assigning a value to a callback. In other words, in our software, it is sometimes possible to assign or not assign a variable value. So optional. std::optional may save us from the null checks we make when using callbacks.

For example, let’s consider the case of a function returning a failed value. Yes we can find different solutions. For example, we can deduce that a function returning an int value is an error if it returns -1.

Well, if the return value is double, what kind of value should we expect it to return in case of an error? How can we find a solution to this? There is a function that returns a double value, and if the function encounters an error it will need to report the error to the outside. What should function return?

Let’s think of something optional. There is a conditionality. That is, a value will be expected to be assigned under certain conditions, and a value will not be assigned in error situations.So optional.

This feature was provided to us with C++17 !!!

std::optional is a C++ feature introduced in C++17, which is defined in the <optional> header. It is a class template that represents an optional value, meaning a value that may or may not be present. std::optional is used to model situations where a function may or may not return a value.

So it is normal for it not to have a value as well as for it to have a value.

An object of type std::optional<T> may or may not hold a value of type T at a particular point in the program’s runtime. As said before, it is normal for it not to have a value as well as for it to have a value. So how can such a class be implemented? Maybe we can think of something like the follows.

Memory Requirement

An object of type optional<T> actually has a memory space of sizeof(T) + sizeof(bool). The bool type data element of the class is used as a flag to indicate whether the std::optional object has an object of type T. We can wrap any type of object with a bool flag variable to form a class. The data element used as a flag in the wrapped structure can inform the user codes whether a value is held or not. These are called nullable types.

If std::optional is not exist, we can implement a structure like above.

To use std::optional you must include the <optional> header file. The member functions, modifiers and observer functions are like follow;

Member functions

We can create optional values ​​like the examples below.

#include <optional>
#include <iostream>
#include <vector>
#include <any>

int main()
{
std::optional<int> optnl1; /* Empty */
std::optional<double> optnl2{}; /* Empty */
std::optional<int> optnl3{ std::nullopt }; /* Empty */
std::optional<int> optnl4 = std::nullopt; /* Empty */
/* with CTAD (class template argument deduction) */
std::optional optnl5{ 13.4 }; /* optional<double> */
std::optional optnl6{ std::string("cengizhan") }; /* optional<string> */
std::optional optnl7{"varli"}; /* optional<const char *>*/
std::optional<std::vector<int>> optnl8{ {1, 2, 3, 4, 5, 6} };
std::optional<std::any> optnl9{ 10 };
}

Observer functions of std::optional

operator *

There is more than one way to access the value held by the optional object. We can access the object or the elements of that object with the content operator and arrow operator functions. If the optional object is empty, undefined behavior occurs. In other words, in such an access, no error object is sent (no exception is thrown). We should pay attention to this !

#include <optional>
#include <iostream>

int main()
{
std::optional<std::string> optnl{ "Cengizhan" };
std::cout << *optnl << "\n";
*optnl += " Varli";
std::cout << *optnl << "\n";

optnl = std::nullopt;
std::cout << *optnl << "\n"; /* Undefined Behevier */

}

value() function

It is safer for us to use the value() function instead because it throws exception. If the value function is called for an empty optional object, an error object of type std::bad_optional_access is throw.

#include <optional>
#include <iostream>

int main()
{
std::optional<std::string> optnl{ "Cengizhan Varli" };
std::cout << optnl.value() << "\n";
optnl = std::nullopt;

try {
std::cout << optnl.value() << "\n";
} catch (const std::bad_optional_access &ex) {
std::cout << ex.what() << "\n";
}
}

value_or() function

The value_or() member function of the std::optional class is a function that returns a default value if the std::optional object is empty or does not contain a value. It allows you to access a value without explicitly checking whether a value is present. If the std::optional contains a value, it returns the contained value; otherwise, it returns the specified default value.

The value_or() function is defined as follows:

T value_or(const T& default_value) const;
  • T is the type of the value stored in the std::optional object.
  • default_value represents the default value to be returned when no value is present

Here’s an example of using the value_or() function:

#include <iostream>
#include <optional>

int main() {
std::optional<int> opt = 10;

std::cout << "Value: " << opt.value_or(0) << std::endl;

opt = std::nullopt;

std::cout << "Value: " << opt.value_or(0) << std::endl;

return 0;
}
Output

In this example, the value_or() function returns 10 because the opt optional contains a value. However, it returns 0 (the default value) for the empty opt optional because it does not contain a value. This is useful in situations where you want to provide a default value instead of explicitly checking for the presence of a value within the std::optional object.

Modifier functions of std::optional

reset() function

This function resets the std::optional to a disengaged state, meaning it no longer contains a value.

std::optional<int> opt = 10;
opt.reset(); /* opt is now in a disengaged state. */

swap() function

The swap() function exchanges the content of two std::optional objects.

std::optional<int> opt1 = 10;
std::optional<int> opt2 = 20;
opt1.swap(opt2); /* opt1 contains 20, opt2 contains 10. */

emplace() function

With this function, we can start an object in the memory space within the optional object without copying. As you know, the emplace function uses the perfect forwarding mechanism, just like the emplace functions of the container classes in the standard library.

std::optional<std::string> opt;
opt.emplace("Hello World!");

When the emplace function is called for an optional object that holds a value, the optional object calls the destructor function of the object it is holding:

#include <iostream>
#include <optional>

class myClass {
public:
myClass() {
std::cout << "myClass()\n";
}

myClass(std::string) {
std::cout << "myClass(string)\n";
}

myClass(int) {
std::cout << "myClass(int)\n";
}

~myClass() {
std::cout << "~myClass()\n";
}
};
int main()
{
std::optional<myClass> os;

os.emplace();

os.emplace("Cengizhan");

os.emplace(22);
}
Output

Most Commonly Used Cases

  • A function returning a value of type optional. Some functions can return a value based on a condition. However, they may not have values ​​to return if the condition is not valid. In other words, it is normal that the function will not return a value as much as it will return a value. In such cases, the return value of the function may be of type optional.
#include <optional>
#include <iostream>

std::optional<int> convertIntegerValue(const std::string& str)
{
try {
return std::stoi(str);
}
catch (...) {
return std::nullopt;
}
}

int main()
{
auto opt = convertIntegerValue("13032022");

if ( opt )
std::cout << "Valid\n";
else
std::cout << "InValid\n";
}
  • It is possible for a function’s parameter variable to be of type optional like this;
#include <iostream>
#include <optional>

void processOptionalValue(std::optional<int> value) {

if (value.has_value()) {
std::cout << "Value: " << value.value() << std::endl;
}
else {
std::cout << "No Value !" << std::endl;
}
}

int main() {

std::optional<int> optWithValue = 22;
std::optional<int> optWithoutValue = std::nullopt;

processOptionalValue(optWithValue);
processOptionalValue(optWithoutValue);

return 0;
}

--

--