std::optional: A Joyful Little Box for Engineers in C++17

When writing C++ projects, do you often encounter scenarios like this: you write a function that may return a value or may not find a result, so you return <span>-1</span>, <span>nullptr</span>, or simply use a boolean value with an output parameter.

These “workarounds” can be used, but they always feel a bit awkward.

C++17 brings us a more elegant tool — <span>std::optional</span>, which helps you naturally express the semantics of “this value may exist or may not exist”.

Today, let’s talk about its usage and application scenarios. 🚀

🔧 Basic Concepts and Operations

<span>std::optional<T></span> can be seen as a “box” that either contains a value of type <span>T</span> or is empty.

Include the header file:

#include <optional>

✅ Creation and Assignment

std::optional<int> a;       // empty
std::optional<int> b = 10;  // has value
b = 20;                     // reassign
b.reset();                  // clear

🔍 Check for Value:

if (result.has_value()) {
    // has value
}

You can also write it directly:

if (result) {
    // has value
}

🔓 Unpack Value:

std::optional<int> opt = 10;
int value = *opt;  // direct unpacking, not recommended as it may throw an exception
int value = opt.value_or(0);  // recommended to access with a default value, returns 0 if opt is empty

Application Scenarios 🎯

1. Representing a “Possible Failure” Return Value

The most common usage is to replace “special values” to indicate failure.

std::optional<int> FindIndex(const std::vector<int>& vec, int target) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (vec[i] == target) {
            return static_cast<int>(i);
        }
    }
    return std::nullopt; // not found
}

This way, the caller can clearly make a judgment:

auto idx = FindIndex({1,2,3}, 2);
if (idx) {
    std::cout << "Found at " << *idx << "\n";
} else {
    std::cout << "Not found\n";
}

Isn’t it clearer than returning <span>-1</span>? ✨

2. Replacing “bool + Output Parameter”

Many C-style APIs are designed this way:

bool GetResult(int& out_val);

When calling, you need to prepare a variable first, which will only be assigned if successful, making it look quite awkward.

Using <span>std::optional</span>, we can express it more naturally:

std::optional<int> GetResult() {
    if (/*success*/) {
        return 42;
    }
    return std::nullopt;
}

auto r = GetResult();
if (r) {
    std::cout << "Result: " << *r << "\n";
} else {
    std::cout << "No result" << "\n";
}

This way, the function becomes more concise, no longer requiring additional output parameters. 🌟

3. Representing “Optional Parameters” as Function Parameters

Sometimes when we write functions, we want a certain parameter to be optional. Previously, we might have used default values <span>""</span> or <span>-1</span> as a workaround.

Now, we can use a more intuitive syntax:

void PrintMessage(std::string msg, std::optional<std::string> prefix = std::nullopt) {
    if (prefix) {
        std::cout << *prefix << ": ";
    }
    std::cout << msg << "\n";
}

PrintMessage("Hello");
PrintMessage("World", "Debug");

You can either pass a value or not pass anything at all. 👐

Conclusion 🍵

<span>std::optional</span> is a small yet beautiful tool introduced in C++17, and its core function is:

👉 To make the semantics of “value exists / does not exist” clearer, avoiding the use of <span>-1</span>, <span>nullptr</span>, and other ambiguous placeholders.

Next time you write an interface, consider using <span>std::optional</span>, and I believe you will come to love it. 😎

Leave a Comment