Functional programming languages like Haskell popularized a concept of optionality, so even old languages like Java and C++ started offering optional types. They are very useful for communicating on the API level that sometimes function has no meaningful value to return. For instance, in case a dictionary has no value associated with provided key, the return type is std::optional<Value>. Previously, APIs would have to resort to sentinel values like -1 for ints or nullptrs, but this becomes problematic in cases when -1 or nullptrs are valid values, making it impossible to differentiate absence of value from valid values that just happen to be the same as sentinels. To handle this, APIs can provide a separate “contains” method, which comes with its own set of issues:
we now have to perform 2 lookups in case value is present
we lose atomicity - having a separate contains and find lookups makes it impossible to design efficient concurrent APIs since we lose atomicity
Ok, so std::optionals are great. Does this mean that we should always use it? The answer is, as always, it depends. Its ergonomics and safety checks do not come for free. For example
results in the following assembly
Not the end of the world, but certainly worse than
So what’s the moral of the story? There is no silver bullet and in case performance matters, sometimes it pays off to use good old sentinels.