C++

C++ Std::Optional

The “std::optional” feature is provided in C++17. The “std::optional” allows a type-safe representation of optional values or a choice of having a value. A template class called “std::optional” contains an optional value which might or might not contain a valid value. It is a more secure substitute to represent the empty or optional values than raw pointers or other techniques. The “std::optional” minimizes the possibility of null pointer dereference errors by requiring the user to explicitly verify whether a value exists before retrieving it.

Example 1:

The “optional” and “iostream” header files are imported in this code. We must import these header files so that we can easily access the functions that are defined in them. After this, we include the “namespace std” so we don’t have to type it with functions separately like “std::optional” and “std::cout”. We use “namespace std” here. So, now, we place the “optional” or “cout” without typing “std” with them.

Then, we invoke main() and place “optional” and set it to “int” and declare “myNum”. It is the syntax for declaring the “std::optional” variable. Then, we initialize another variable named “value” and assign a value to the “myNum” variable by utilizing the value_or() function. We pass “99” in this function, so it assigns this “99” to the “muNum” variable if there is no value present and store it in the “value” variable. Then, we place “cout” underneath this which aids in displaying the value that we assigned to the variable above it.

Code 1:

#include <optional>

#include <iostream>

using namespace std;

int main() {

  optional<int> myNum;

  int value = myNum.value_or(99);

  cout << "The value of myNum is: " << value << endl;

  return 0;

}

Output:

Here, we can notice that “99” is displayed which means that the value was not present above and the value that we added is assigned to that variable.

Example 2:

We first include the header files and place the “namespace std”. Now, underneath this, we declare the “std::optional” function which is the “divideFunc()”. The “dividend” and “divisor” are the two parameters of this function. We then utilize “if” below it where we add a condition that says “divisior != 0”. If this is satisfied, it returns the answer of this division as we add “return” inside this. Otherwise, it returns “nullopt” which means no safe-type value. Now, we invoke the main(). To infer to the “std::optional”, we place “divideFunc()” and adde “27, 3” in it and assign the result to the “quotient” variable.

Here, we utilize the “auto” keyword so it automatically adjusts its data type. After this, we add the “if” where we utilize the “has-value” which determines whether a type-value is received. Then, we place the “cout” which renders the outcome that is stored in the “quotient” variable, and the “else” part contains a statement which renders that the divisor is zero.

Code 2:

#include <iostream>

#include <optional>

using namespace std;

    optional<int> divideFunc(int dividend, int divisor) {

  if (divisor != 0) {

    return dividend/divisor;

  }

  return nullopt;

}

int main() {

  auto quotient = divideFunc(27, 3);

  if (quotient.has_value()) {

    cout << "The quotient is: " << quotient.value() << endl;

  } else {

    cout << "The Divisor is zero here" << endl;

  }

  return 0;

}

Output:

The output renders the result after division which means that the divisor is not zero. In this instance, “std::optional” is utilized to determine whether a value exists or not type-safely.

Example 3:

Here, we are declaring the “std::optional” variable which is the “number” inside the main(). Then, we utilize “if” in which we place the has_value() function with this “number” variable. This checks whether there is a value or not in this “number” variable. If the “number” variable contains a value, it renders the statement that we added after “if”. Otherwise, it renders the statement that we placed after “else”.

Now, we initialize the “number” with “92” and utilize “if” again underneath this where the has_value() function is added with the “number” variable in the “if as the condition”. This determines whether or not the “number” variable has a value. The sentence that we add after “if” is rendered if the “number” variable has a value. If not, the statement that we place after “else” is rendered.

Code 3:

#include <iostream>

#include <optional>

int main() {

  std::optional<int> number;

  if (number.has_value()) {

    std::cout << "The number is present: " << number.value() << std::endl;

  } else {

    std::cout << "The number is not present." << std::endl;

  }

  number = 92;

  if (number.has_value()) {

    std::cout << "The number is present: " << number.value() << std::endl;

  } else {

    std::cout << "The number is not present." << std::endl;

  }

  return 0;

}

Output:

This renders the “else” part first because we don’t assign any value to the “std::optional” variable. Then, we assign a value to this variable to display that value in the next line.

Example 4:

Now, we declare three “std::optional” variables which are “n1”, “n2”, and “n3”. We also assign the values to “n2” and “n3” variables which are “29” and “45”, respectively. The “n1” variable of the “std::optional” class is empty here. Now, we utilize the “boolalpha” which aids in returning the return in the “true” or “false” form rather than “1” and “0”.

After this, we utilize the relational operators between these “std::optional” variables and place each statement inside the “cout” so it would also render the result of the comparison that we added. First, it checks that “n3 > n2”, then “n3 < n2”, “n1 < n2”, “n1 == std::nullopt ”. Here, “nullopt” is utilized for comparing the no safe-type value or null. Then, we check “n2 == 49” and “n3 == 88” inside the “cout” statement separately.

Code 4:

#include <optional>

#include <iostream>

int main()

{

  std::optional<int> n1;

  std::optional<int> n2(29);

  std::optional<int> n3(45);

  std::cout << std::boolalpha;

  std::cout << "The n3 > n2 "<< (n3 > n2) << std::endl;

  std::cout << "The n3 < n2 "<< (n3 < n2) << std::endl;

  std::cout << "The n1 < n2 "<< (n1 < n2) << std::endl;

  std::cout << "The n1 == null "<< (n1 == std::nullopt) << std::endl;

  std::cout << "The n2 == 49 "<< (n2 == 29) << std::endl;

  std::cout << "The n3 == 88 "<< (n3 == 88) << std::endl;

}

Output:

The C++ program that we previously mentioned compares various variable values of the “std::optional” type while concurrently printing the outcome to the output.

Example 5:

The header files that are included in this code are “iostream”, “fstream”, “optional”, and “string”. The “fstream” contains the definition of both functions which are “ofstream” and “ifstream” that we need in this code. Now, we include the “namespace std”, so we don’t place it separately with each function. Then, we utilize the “std:optional” and declare a function with the name “ReadFileFunc” in which we pass “const string& f_Name” as the argument.

Then, we have “ifstream” that helps to read the file whose name will be added to the “f_name” variable. Then, we utilize “if” in which we include the condition that says if the file is not opened, it returns “nullopt” as we added it below the “if” statement. Then, we create another function which is “fileContent” that aids in writing the content to the file if the file is opened. Here, we place the “return fileContent” again which also returns the content that we added to the file after opening.

Now, we call the “main()” here in which we initialize the “f_Name” variable with the “Sample.txt” file name that we want to open. Then, we call the “ReadFileFunc()” here and pass the “f_Name” variable in this function which tries to read the file and stores its content in the “f_content” variable. Underneath this, we utilize the “has_value()” with the “f_content” variable in “if”. If this variable contains a value, it also renders it as we added the “cout” below “if” in which we also placed the “f_content”. Otherwise, it shows the error that we added after “else”.

Code 5:

#include <iostream>

#include <fstream>

#include <optional>

#include <string>

using namespace std;

  optional<string> ReadFileFunc(const string& f_Name) {

  ifstream myFile(f_Name);

  if (!myFile.is_open()) {

    return nullopt;

  }

  string fileContent((istreambuf_iterator<char>(myFile)), istreambuf_iterator<char>());

  return fileContent;

}

int main() {

  const string f_Name = "Sample.txt";

  auto f_content = ReadFileFunc(f_Name);

  if (f_content.has_value()) {

    cout << "The content of the file is :\n" << f_content.value() << endl;
 
  } else {

    cerr << "Error: File not opened here " << f_Name << endl;

  }

  return 0;

}

Output:

Here, it shows the error statement which we added in the “else” part as the outcome of the given code.

Conclusion

In this tutorial, we explored the strong C++ feature which is “std::optional” and explained that it offers a standardized method of representing the optional values, doing away with the requirement for null references and enhancing clarity and code safety. We learned that it also improves the ability to explain the complicated issues and deal with mistakes with grace.

About the author

Omar Farooq

Hello Readers, I am Omar and I have been writing technical articles from last decade. You can check out my writing pieces.