top | item 17274358

Modern C++ Features – std::optional

86 points| tempodox | 7 years ago |arne-mertz.de | reply

113 comments

order
[+] th0br0|7 years ago|reply
The worst part about this is that Apple has decided to once again violate the standard. They purposefully eliminated the `value()` accessor due to ABI issues as far as I understand from the stack overflow comments: https://stackoverflow.com/questions/44217316/how-do-i-use-st...

My recommendation would be to just use https://github.com/martinmoene/optional-lite for cross-platform stuff.

[+] klodolph|7 years ago|reply
"Violate the standard" might be a bit harsh. The fact that you are using <experimental/optional> instead of <optional> as your header should indicate that something else is going on.

Practically speaking, it takes a while for features implemented upstream to make it into your OS of choice. If these are really critical features, you can get third-party implementations of <optional> or you can compile your own toolchain, neither of these are particularly difficult options. It can be a bit frustrating, yes, when language feature arrives on a different schedule in the library and compiler (which happens).

Mind you, #include <optional> doesn't work on my Linux box either, with GCC. And if you look at the GCC web page, https://gcc.gnu.org/projects/cxx-status.html#cxx17, it says that GCC's support for C++17 is "experimental". I know that GCC != Clang, but let's give them some time to test things before a million projects get the code compiled in and we're stuck with the implementation.

[+] spullara|7 years ago|reply
It looks like this is just temporary until the feature is no longer experimental so they can modify the standard library.
[+] cpeterso|7 years ago|reply
A possible std::optional footgun is that an optional object can implicitly be converted to bool, returning optional has_value(). For optional objects that may hold value types that themselves can be used in a boolean context (like bool or a pointer value), then a programmer may confuse the implicit has_value() check with returning the real value().

  std::optional<bool> opt(false);
  assert(opt); // true even though *opt is false!
http://en.cppreference.com/w/cpp/utility/optional
[+] nneonneo|7 years ago|reply
Is this really different than a pointer (or smart pointer) pointing to a false-y object? An optional isn’t implicitly convertible to its contained type - you have to use * or .value(), so it seems like there wouldn’t be much confusion.
[+] bobbyi_settv|7 years ago|reply
An optional containing a pointer seems pretty dubious. You would usually either just use a pointer and have null mean empty or use an optional containing the pointed-to type.

An optional pointer is a weird tri state thing where you can have empty or a null pointer or a non-null pointer.

[+] hoppelhase|7 years ago|reply
Now imagine you are using type inference using `auto` and you are refactoring some functions from returning `bool` to `optional<bool>`.

Good luck with that.

[+] Animats|7 years ago|reply
The pointer dereferencing operators and -> are implemented, but without the std::bad_optional_access – accessing an empty std::optional this way is undefined behavior.*

Oh, please. Raise an exception or panic. But "undefined behavior" on dereferencing this new form of "null"? That's no good.

[+] klodolph|7 years ago|reply
Edit: Rewritten.

You say, "Oh, please." But this feature is consistent with the design mandate of the C++ language... the very foundation of its design is that you don't pay for safety if you don't want to. So I am not sure what you are trying to say.

Honestly... why aren't you just using a different language? Practically every other language invented in the past 30 years has the safety features it sounds like you want. So, why come into a discussion about some new C++ feature and complain about the fact that a safety feature is optional? This is the way it always has been for C++. This is the way pointer dereferencing works for all the new smart pointers. Dereferencing std::shared_ptr and std::unique_ptr... both unsafe, equally unsafe as std::optional. This is the way std::vector works. They're all unsafe unless you specifically use certain safer accessors like .at(). The fact that dereferencing a std::optional is unsafe is consistent with decades of changes to C++.

[+] zik|7 years ago|reply
The standard way of dereferencing std::optional is with .value() and that does provide a check and an exception. Using * and -> is equivalent to invoking "unsafe" in other languages. You get the semantics you ask for. If you don't want unsafe behaviour it's really as simple as using the safe operator. I don't honestly see what the problem is here.
[+] keldaris|7 years ago|reply
As much as I dislike most of "modern C++", the one thing they have consistently kept intact is that all the safety guarantees, at least insofar as they have performance implications, are optional. That's how it should be, by design.

There are plenty of languages that make implicit, opaque tradeoffs in order to give you some measure of protection from programmer errors. C++ was never meant to be among them.

[+] jlebar|7 years ago|reply
This is consistent with e.g. std::vector::at vs operator[]. The former throws an out-of-bounds exception, the latter has ub on bad index.

Like it, don't like it, that's cool. But it is consistent.

[+] alexeiz|7 years ago|reply
If you want exceptions, use 'value()'. If you want minimum runtime checks you use operator*/->. This is perfectly consistent with the spirit of C++ to give you maximum control over your code.
[+] TheAnig|7 years ago|reply
With all the modern features being used, I wonder how one would go about learning modern C++.
[+] Koshkin|7 years ago|reply
If you know the basics, cppreference.com is your best bet.
[+] anaphylactic|7 years ago|reply
That's a good point, and it's not just a problem with C++. As any project matures, the voices of current users drown out the voices of new users, and as a result discoverability gets thrown out the window. How would you engineer a project so as to avoid this fate?
[+] NegativeLatency|7 years ago|reply
Can’t you still return null from a function? So even if your return type was optional, you’d have to check if it was null in some cases before unwrapping it.

(Edit: Limited c++ experience, thanks for explaining)

[+] slavik81|7 years ago|reply
No. In Java every object variable is a reference type. They're all implicitly pointers. Hence why all objects can be null. Java just hides all the dereferencing away. "Object myobject;" creates a pointer/reference rather than an actual object.

In C++ you can have pointers to objects, but they're explicitly denoted by an asterisk. The C++ equivalent would be "Object* myobject;"

However, you can also directly have object variables in C++. That's not something that exists in Java. A variable declared like, "Object myobject;" can never be null. The myobject variable is a value, just like i in "int i = 5;" is a value.

[+] beached_whale|7 years ago|reply
No, this isn't java/c#. In C++ only a pointer can be set to nullptr or NULL. std::optional is a value that can store a type T(T is a place holder for a type name). If T is a pointer (such as T * or int *...) then yes you could return null. But that is rare and looks weird.

So the value of an optional is either a valid value or it has no value. The default constructed std::optional<T> has no value

[+] jcelerier|7 years ago|reply
uh ? unlike languages such as java, or c#, objects aren't nullable in C++, only pointers to objects.

You can't do for instance

    struct foo {
      // whatever
    };

    foo my_function() {
      return nullptr;
    }
and thus,

    std::optional<int> my_function() {
      return nullptr;
    }
does not compile (rightly).
[+] Buge|7 years ago|reply
No you can't. "null" isn't a specific thing in c++. There's nullptr, NULL, and 0. NULL is just a fancy name for 0.

If for example you have a function with a return type of string, it must return a string. It cannot return nullptr, NULL, or 0. It could return an empty string. But in some cases you might want to distinguish between empty string and no result, in that case you would want to the return type to be std::optional<std::string> .

[+] HippoBaro|7 years ago|reply
std::optional is designed for value semantic. It wouldn't make sense to use it to return a pointer-type.
[+] awestroke|7 years ago|reply
Sounds like a useless feature in that case
[+] adamnemecek|7 years ago|reply
I wonder if at some point there will be cpp that's kinda like kotlin to java. Source code compatible but a clean cut.
[+] 0xmaverick|7 years ago|reply
I find this useful for older paradigms where you want an error as well as pass in something to fill in as the output. Instead of doing bool foo(OutFoo* out_foo) or overloading Id getId(); // returns -1 if not present. You could use base::Optional<OutFoo> / base::Optional<Id> in each case respectively.
[+] klodolph|7 years ago|reply
You may find std::variant also very useful. You can do something like std::variant<Error, Foo>.
[+] sharpercoder|7 years ago|reply
Optional is a typical feature that should be baked into the syntax. If used properly, it will be used throughout codebases, and when it is used, it adds simple, replaceable syntax.
[+] anaphylactic|7 years ago|reply
I disagree. If a perfectly good optional type can be implemented without making new syntax, why bother making new syntax? It obscures what's going on under the hood, makes implementation needlessly complex, and increases mental overhead for users.
[+] geezerjay|7 years ago|reply
I fail to see how forcing syntax changes (thus breaking all IDEs) is preferable to simply adding a class, or how using a custom specialized keyword brings anything but problems when trying to replace a component.
[+] jstimpfle|7 years ago|reply
> In programming, we often come across the situation that there is not always a concrete value for something

In database terms, the need for null values means that the data schema is denormalized. With a simpler approach there is seldom any need for these "not applicable" values.

And that's the problem with all languages with special support for "nullable" values: They glorify bad data structure design. I've seen codebases where 30-50% of the lines is just handling of null values that SHOULD NOT BE THERE and where nothing meaningful (or even functional) happens when the value is actually null.

Goes well together with OOP: In the name of isolation, each object is abstracted from all context necessary to construct a straightforward and correct program. The result is more and more meaningless boilerplate and burnt out developers.

Personally I'm fine with not assigning any value to "not applicable" variables. Or putting a sentinel there, like -1 or NULL. But adding a physical case (i.e. changing the type) to support "not applicable" cases is just not a good idea.

[+] mattnewport|7 years ago|reply
I think it's more helpful to think of C++ std::optional like Maybe in a functional language rather than as like a nullable type. Its primary use case is as a return value from functions, not as a data member. There are many functions where this can make good sense: parsing a string as a number, searching for an element in a collection, trying to load something from a file, taking a square root, etc.
[+] marcosdumay|7 years ago|reply
> In database terms, the need for null values means that the data schema is denormalized.

And nobody ever use a fully normalized schema in practice, because it's cumbersome and gets in the way of solving the problem.

And that's for databases, that are optimized stable storage and data coherence, not for performance. A fully normalized schema is a performance disaster if applied to running applications.