DEV Community

Pierre Gradot
Pierre Gradot

Posted on • Edited on

std::optional from C++17 vs custom type for optional value

C++17 has introduced a very useful template class, std::optional:

The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.

I have been using this feature a lot since 2017, even for a simple optional integer. Today, I got curious: is this template type effective compared to an equivalent type that I would write myself to achieve the same behavior?

Good question, thank you πŸ˜†

Let's try to write some code and use Compiler Explorer to compare both solutions: std::optional vs custom type for optional value.

First, let's define two equivalent types:

#include <optional>

using StdOptionalInt = std::optional<int>;

struct CustomOptionalInt {    
    bool has_value;
    int value;
};
Enter fullscreen mode Exit fullscreen mode

Then, let's write two functions that test if the value is available and return either this value (if available) or a default value (if not available):

int getStdOptional(const StdOptionalInt& o) {
    return o.has_value() ? o.value() : 0;
}

int getCustomOptional(const CustomOptionalInt& o) {
    return o.has_value ? o.value : 0;
}
Enter fullscreen mode Exit fullscreen mode

Finally, compile the code with Compiler Explorer and compare the output assembly codes (you can try by yourself here):

with_compiler_explorer

Yeah! πŸ˜ƒ The 2 functions generate the same assembly code. There is no difference of performance. Notice the static assertion at the end of the source code: because the code compiles, it means the footprint are the same. The behavior is the same with gcc and clang for x86-64.

As a conclusion, std::optional is as efficient as a custom type to represent an optional integer value. Don't implement your own type, simply use the standard type. You may even get better performance using std::optional, as explained on cppreference:

As opposed to other approaches, such as std::pair<T,bool>, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.


By the way, if you happen to speak French:

  • I wrote an article to explain to how to use std::optional.
  • I made a video to present Compiler Explorer.

Top comments (2)

Collapse
 
sandordargo profile image
Sandor Dargo

Stunning that the generated code is the same. Have you had a look into one od the standard implementations?

github.com/gcc-mirror/gcc/blob/mas...

Collapse
 
pgradot profile image
Pierre Gradot • Edited

To be honest, I wasn't surprised. I was not expecting the exact same code but I was expecting something similar, for various reasons:

  1. has_value() is just about returning a bool. No reason to cost more than a simple access to member variable.
  2. value() is just a test to decide to return the value or throw an exception. Nothing fancy here.

We may expect the exception to cost a more than a simple access to a member variable. So why ?

This leads me to reason 3. Over the years (and hours spent in Compiler Explorer, you definitively must use it!), I have learned something important: modern compilers are amazing at optimizing code. Way much better that you and me.

And here, take a better look at the code: it tests if the std::optional has a value and get the value conditionally. The compiler then know the exception cannot be thrown and optimizes that out.

Change the code and get a completely different result:

int getStdOptional(const StdOptionalInt& o) {
    return o.value();
}
Enter fullscreen mode Exit fullscreen mode

This generates all the code to handle the exception and it's a lot a code.

I something take a look at the source of standard features. I simply to a CTRL+click in my IDE. std::optional seems crazy but it does much more than my custom type. It is made by and for the standard library developers. And this leads me to reason 4 : these developers are way better than you and me to create powerful, flexible, easy-to-use, robust code ;)