
Modern C++ Features – std::optional - tempodox
https://arne-mertz.de/2018/06/modern-c-features-stdoptional/
======
th0br0
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...](https://stackoverflow.com/questions/44217316/how-do-i-use-stdoptional-
in-c)

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

~~~
klodolph
"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](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.

~~~
th0br0
GCC moved it to <optional> with the release of GCC7. Note that we're now at
GCC8, clang 4 added it - I believe we're now at clang 6.

Therefore, it's by far no longer something that's experimental IMHO.
(standardised in C++17 after all)

------
cpeterso
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](http://en.cppreference.com/w/cpp/utility/optional)

~~~
bobbyi_settv
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.

~~~
Koshkin
Semantically though, not having a pointer value and having a value - even null
- are two different things. For example, doing pointer arithmetic in the
former case would be meaningless.

~~~
netheril96
Null pointer arithmetic is undefined, so just as pointless as arithmetic on an
empty optional pointer. Your example fails to prove that optional pointer has
any valid use.

~~~
Koshkin
Sure, my point might seem theoretical; to explain further, the difference is
that when std::optional pointer is used correctly its behavior in the absence
of a value is perfectly defined! Which is a good thing.

------
Animats
_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
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++.

~~~
Stratoscope
Let's keep things civil please. Saying "I can only imagine that you're trying
to start a fight" is rarely helpful.

~~~
klodolph
Okay, rewritten. I honestly thought the parent comment was baiting, since
complaining about C++ safety is... well, rarely helpful and usually just
starts arguments.

~~~
Stratoscope
Thank you for the rewrite, much appreciated.

If I may offer a tip, when someone takes a position that seems unreasonable or
baiting or argument-prone, I try to apply the principle of charity: I assume
that their intentions are good.

Of course I don't always succeed at this! ;-)

~~~
klodolph
Sometimes the principle of charity is quite harsh, because in some cases if
you assume that someone has good intentions, you come across as condescending
and make it sound like you think they're ignorant. So I don't think that the
principle of charity is always the right approach. The tone of the original
comment starting with "Oh please" doesn't incline me towards a charitable
interpretation, and it still doesn't.

~~~
Stratoscope
You are certainly right about that. Perhaps this is where it becomes an art
instead of a science. How can we create a positive conversation that we can
all learn from, even when people say such obviously provocative things as "Oh
please"?

If someone takes that kind of attitude, so what? You'll never lose by taking
the high road; I've found it to be a very rewarding exercise to try to be
chill regardless. Of course as I mentioned, I don't always succeed.

At least I _try_ to be like the old Shadio Rack:

"You've got answers? We've got questions."

~~~
klodolph
Emphatically disagree. You can lose by taking the high road, because it costs
time and energy. Being chill regardless is an unhealthy habit, I've found it
better to allow myself to feel whatever I happen to be feeling (mindfulness),
and share my feelings with others when appropriate. This will inevitably be
"not chill" from time to time but it's healthier.

------
boltzmannbrain
Been using boost for this (what else is new?):

[https://theboostcpplibraries.com/boost.optional](https://theboostcpplibraries.com/boost.optional)

[https://stackoverflow.com/questions/22227839/how-to-use-
boos...](https://stackoverflow.com/questions/22227839/how-to-use-
boostoptional)

~~~
beached_whale
constexpr I think is the big difference. Also the name of the empty class to
reset the value to none, boost is boost::none whereas the std has
std::nullopt_t. I prefer the boost name of none as nullopt sounds and looks
too much like null.

------
TheAnig
With all the modern features being used, I wonder how one would go about
learning modern C++.

~~~
Koshkin
If you know the basics, cppreference.com is your best bet.

~~~
stochastic_monk
And reading real code. It’s important to see how these pieces all go together.
Just reading cppreference is like familiarizing yourself with legos without
seeing how to build something with them.

------
NegativeLatency
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
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.

~~~
repsilat
To expand on this:

In C++ a useful heuristic could be to think of values like Java primitives.

A C++ integer takes up some amount of space. Maybe it's four bytes on your
platform. Integers on your platform take up four bytes. Not "four bytes plus a
pointer", but four bytes and that's it.

In those four bytes you can store 2^32 values. There's nowhere else to store
whether the integer is null -- if you wanted to signal to someone that the
value was "missing", you would either have to,

\- Come up with your own "in-band" signal, like "-(2^31) means 'missing'", or

\- Have an out-of-band signal, like a separate boolean variable, or keep a
pointer to "integer or null". (Pointers being nullable is arguably kinda weird
still.)

Functions that return an integer in C++ on your platform will put four bytes
"in the right place" on the stack. (Or in a register? I don't really know what
calling conventions look like...)

Again, there's nowhere to signal that the value is missing -- if you don't
fill in that memory location or that register, well, there will just be some
other (undefined) value sitting in its place that the caller will read. Ditto
functions that expect integer arguments etc.

Now, this has some interesting consequences: for stack-allocated values in
C++, "object identity" tends to be preserved less often than in Java. When you
put an object into a `vector`, you'll probably put a copy there. When you
mutate that copy or call some function on it, you're not mutating the
original. You can get back to "Java semantics" of object identity and
nullability etc by using pointers and explicit heap storage.

~~~
cesarb
> Functions that return an integer in C++ on your platform will put four bytes
> "in the right place" on the stack. (Or in a register? I don't really know
> what calling conventions look like...)

On a register. I don't know of any mainstream platforms which return simple
values like an integer through the stack.

For larger values (larger than a word or a couple of words), on the other
hand, usually the calling function passes the called function a pointer to a
location on the stack where it should write the result.

Either way, like you said "there's nowhere to signal that the value is
missing".

~~~
kupiakos
Nitpicky detail: a lot of the time, the pointer variable passed to be written
to is not on the stack, e.g. using strcpy with a malloc'd buffer.

~~~
mianos
I believe he is taking about the native retuning semantics, which is in a real
register for simple types or organised to be on the stack by the caller or
register frame for a larger value type. Point being, it's a compact primitive
reflection of the machine, not much more.

------
adamnemecek
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
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
You may find std::variant also very useful. You can do something like
std::variant<Error, Foo>.

------
sharpercoder
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
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.

~~~
Koshkin
To be fair, not all languages make this easy.

------
jstimpfle
> 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.

~~~
marcosdumay
> 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.

~~~
mianos
This is not really true of any mature system. There are many reasons to design
a schema in normal form, including performance, maintainability and space
optimisation. Database design outside start-ups is a fairly mature area and
normal form is well proven good practice.

~~~
magicalhippo
In our application we have a form that has at least 50 fields which are
individually optional. This is in addition to the required fields, and
currently it's all one table.

Are you saying it would be better for performance and space to have all of
those optional fields in separate tables, and that it wouldn't be a huge PITA
to write queries against for say reporting and while debugging?

~~~
jstimpfle
Are these 50 fields "logically" relevant? In other words, do you do
significant joining at these fields? Or in business logic, do you switch much
on the presence of these fields?

I'll assume "no" since they're so many of them and they're individually
optional fields. Maybe most of them are strings and you could simply use empty
strings. Or whatever, it doesn't matter. I'm not saying these cases are
trivial, but they're leaf concerns and not really relevant from a "schema"
point of view.

But if the answer is really "yes", then I'll say that's at least a
maintainability problem (bad coherence).

~~~
magicalhippo
They're fields in a digitized form, essentially. Multiple name, address etc
sections representing people or companies, for example, as well as domain
specific fields which shouldn't be used, are optional or are required
depending on the value of a previous field. Most are strings of various
lengths, but also plenty of numeric (weight etc) fields as well as dates.
Expected arrival date for example, which is optional in most cases but
required in some.

Our UI would have to join in all the fields, as it displays them all in one
window, and our customers would absolutely hate to split filling them up into
multiple windows.

Also, once the form is sent and approved by officials, the data has to be
saved without change for 10 years, like saving the paper version of the form
for 10 years in a filing cabinet. It also needs to be easily accessible for
the users up to 3 years after approval in case one needs to make a correction,
which is essentially done using a special copy of the form.

I'm no db expert by any stretch, I've learned by doing. So not gonna claim
this couldn't have been handled better.

~~~
magicalhippo
To expand slightly. Take one of the name and address sets. If the company has
a valid EORI number, one should _only_ fill in the EORI number in the
organization number field (filling in name etc would lead to error). Otherwise
one should supply name, address, country etc. Depending on the value of
another field, the name of a contact person is required, otherwise it's
optional.

~~~
jstimpfle
I think these kinds of constraints are much easier implemented as code in a
business logic layer. Conventional database schemas with their limited
expressiveness are probably not well suited here.

~~~
magicalhippo
Sure, but that wasn't really the issue. The issue was how to store such data
in an optimal way.

