187

I'm reading the documentation of std::experimental::optional and I have a good idea about what it does, but I don't understand when I should use it or how I should use it. The site doesn't contain any examples as of yet which leaves it harder for me to grasp the true concept of this object. When is std::optional a good choice to use, and how does it compensate for what was not found in the previous Standard (C++11).

6
  • 19
    The boost.optional docs might shed some light. Commented May 31, 2013 at 15:36
  • Seems like std::unique_ptr can generally serve the same use cases. I guess if you have a thing against new, then optional might be preferable, but seems to me that (developers|apps) with that kind of view on new are in a small minority... AFAICT, optional is NOT a great idea. At the very least, most of us could comfortably live without it. Personally, I'd be more comfortable in a world where I don't have to choose between unique_ptr vs. optional. Call me crazy, but Zen of Python is right: let there be one right way to do something! Commented Oct 4, 2015 at 19:04
  • 19
    We don't always want to allocate something on the heap that has to be deleted, so no unique_ptr is not a substitute for optional.
    – Krum
    Commented Feb 17, 2016 at 18:46
  • 5
    @allyourcode No pointer is a substitute for optional. Imagine you want an optional<int> or even <char>. Do you really think it's "Zen" to be required to dynamically allocate, dereference, and then delete - something that could otherwise not require any allocation and fit compactly on the stack, and possibly even in a register? Commented Jun 20, 2017 at 15:17
  • 1
    Another difference, resulting no doubt from the stack vs heap allocation of the contained value, is that the template argument cannot be an incomplete type in the case of std::optional (while it can be for std::unique_ptr). More precisely, the standard requires that T [...] shall satisfy the requirements of Destructible. Commented Jan 31, 2018 at 0:57

4 Answers 4

222

The simplest example I can think of:

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}

The same thing might be accomplished with a reference argument instead (as in the following signature), but using std::optional makes the signature and usage nicer.

bool try_parse_int(std::string s, int& i);

Another way that this could be done is especially bad:

int* try_parse_int(std::string s); //return nullptr if fail

This requires dynamic memory allocation, worrying about ownership, etc. - always prefer one of the other two signatures above.


Another example:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

This is extremely preferable to instead having something like a std::unique_ptr<std::string> for each phone number! std::optional gives you data locality, which is great for performance.


Another example:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

If the lookup doesn't have a certain key in it, then we can simply return "no value."

I can use it like this:

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

Another example:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

This makes a lot more sense than, say, having four function overloads that take every possible combination of max_count (or not) and min_match_score (or not)!

It also eliminates the accursed "Pass -1 for max_count if you don't want a limit" or "Pass std::numeric_limits<double>::min() for min_match_score if you don't want a minimum score"!


Another example:

std::optional<int> find_in_string(std::string s, std::string query);

If the query string isn't in s, I want "no int" -- not whatever special value someone decided to use for this purpose (-1?).


For additional examples, you could look at the boost::optional documentation. boost::optional and std::optional will basically be identical in terms of behavior and usage.

18
  • 14
    @gnzlbg std::optional<T> is just a T and a bool. The member function implementations are extremely simple. Performance shouldn't really be a concern when using it - there are times when something is optional, in which case this is often the correct tool for the job. Commented Jun 27, 2013 at 1:55
  • 8
    @TimothyShields std::optional<T> is a lot more complex than that. It uses placement new and a lot more other things with proper alignment and size in order to make it a literal type (i.e. use with constexpr) among other things. The naive T and bool approach would fail pretty quickly.
    – Rapptz
    Commented Mar 13, 2014 at 22:05
  • 18
    @Rapptz Line 256: union storage_t { unsigned char dummy_; T value_; ... } Line 289: struct optional_base { bool init_; storage_t<T> storage_; ... } How is that not "a T and a bool"? I completely agree the implementation is very tricky and nontrivial, but conceptually and concretely the type is a T and a bool. "The naive T and bool approach would fail pretty quickly." How can you make this statement when looking at the code? Commented Mar 13, 2014 at 22:56
  • 15
    @Rapptz it's still storing a bool and the space for an int. The union is only there to make the optional not construct a T if it's not actually wanted. It's still struct{bool,maybe_t<T>} the union is just there to not do struct{bool,T} which would construct a T in all cases.
    – PeterT
    Commented Mar 28, 2014 at 6:21
  • 14
    @allyourcode Very good question. Both std::unique_ptr<T> and std::optional<T> do in some sense fulfill the role of "an optional T." I would describe the difference between them as "implementation details": extra allocations, memory management, data locality, cost of move, etc. I would never have std::unique_ptr<int> try_parse_int(std::string s); for example, because it would cause an allocation for every call even though there's no reason to. I would never have a class with a std::unique_ptr<double> limit; - why do an allocation and lose data locality? Commented Mar 10, 2015 at 21:44
44

An example is quoted from New adopted paper: N3672, std::optional:

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}
2
  • 14
    Because you can pass the information on whether you got an int or not up or down call hierarchy, instead of passing around some "phantom" value "assumed" to have a "error" meaning. Commented Oct 3, 2013 at 16:35
  • 1
    @Wiz This is actually a great example. It (A) lets str2int() implement the conversion however it wants, (B) regardless of the method of obtaining the string s, and (C) conveys full meaning via the optional<int> instead of some stupid magic-number, bool/reference, or dynamic-allocation based way of doing it. Commented Jun 3, 2017 at 14:15
11

but I don't understand when I should use it or how I should use it.

Consider when you are writing an API and you want to express that "not having a return" value is not an error. For example, you need to read data from a socket, and when a data block is complete, you parse it and return it:

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

If the appended data completed a parsable block, you can process it; otherwise, keep reading and appending data:

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}

Edit: regarding the rest of your questions:

When is std::optional a good choice to use

  • When you compute a value and need to return it, it makes for better semantics to return by value than to take a reference to an output value (that may not be generated).

  • When you want to ensure that client code has to check the output value (whoever writes the client code may not check for error - if you attempt to use an un-initialized pointer you get a core dump; if you attempt to use an un-initialized std::optional, you get a catch-able exception).

[...] and how does it compensate for what was not found in the previous Standard (C++11).

Previous to C++11, you had to use a different interface for "functions that may not return a value" - either return by pointer and check for NULL, or accept an output parameter and return an error/result code for "not available".

Both impose extra effort and attention from the client implementer to get it right and both are a source of confusion (the first pushing the client implementer to think of an operation as an allocation and requiring client code to implement pointer-handling logic and the second allowing client code to get away with using invalid/uninitialized values).

std::optional nicely takes care of the problems arising with previous solutions.

4
  • 1
    I know they're basically the same, but why are you using boost:: instead of std::?
    – David G
    Commented May 31, 2013 at 16:21
  • 4
    Thanks - I corrected it (I used boost::optional because after about two years of using it, it is hard-coded into my pre-frontal cortex).
    – utnapistim
    Commented May 31, 2013 at 16:24
  • 1
    This strikes me as a poor example, since if multiple blocks were completed only one could be returned and the rest discarded. The function should instead return a possibly-empty collection of blocks.
    – Ben Voigt
    Commented May 31, 2013 at 16:26
  • You-re right; it was a poor example; I have modified my example for "parse first block if available".
    – utnapistim
    Commented May 31, 2013 at 16:42
6

I often use optionals to represent optional data pulled from configuration files, that is to say where that data (such as with an expected, yet not necessary, element within an XML document) is optionally provided, so that I can explicitly and clearly show if the data was actually present in the XML document. Especially when the data can have a "not set" state, versus an "empty" and a "set" state (fuzzy logic). With an optional, set and not set is clear, also empty would be clear with the value of 0 or null.

This can show how the value of "not set" is not equivalent to "empty". In concept, a pointer to an int (int * p) can show this, where a null (p == 0) is not set, a value of 0 (*p == 0) is set and empty, and any other value (*p <> 0) is set to a value.

For a practical example, I have a piece of geometry pulled from an XML document that had a value called render flags, where the geometry can either override the render flags (set), disable the render flags (set to 0), or simply not affect the render flags (not set), an optional would be a clear way to represent this.

Clearly a pointer to an int, in this example, can accomplish the goal, or better, a share pointer as it can offer cleaner implementation, however, I would argue it's about code clarity in this case. Is a null always a "not set"? With a pointer, it is not clear, as null literally means not allocated or created, though it could, yet might not necessarily mean "not set". It is worth pointing out that a pointer must be released, and in good practice set to 0, however, like with a shared pointer, an optional doesn't require explicit cleanup, so there isn't a concern of mixing up the cleanup with the optional having not been set.

I believe it's about code clarity. Clarity reduces the cost of code maintenance, and development. A clear understanding of code intention is incredibly valuable.

Use of a pointer to represent this would require overloading the concept of the pointer. To represent "null" as "not set", typically you might see one or more comments through code to explain this intention. That's not a bad solution instead of an optional, however, I always opt for implicit implementation rather than explicit comments, as comments are not enforceable (such as by compilation). Examples of these implicit items for development (those articles in development that are provided purely to enforce intention) include the various C++ style casts, "const" (especially on member functions), and the "bool" type, to name a few. Arguably you don't really need these code features, so long as everyone obeys intentions or comments.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.