C++ Are std::any and std::optional , “driving you up the wall” ?

Driving up the wall. No C++
Driving up the wall. No C++involved.

Well, they were, but not any more. Here is what I have realized since.

std::optional<T>

Is much more “normal” and usable of the two.  It is actually very nice and useful single instance standard & compliant kind-of-a container. And most importantly

You do not need to keep the type of T to use std::optional

That is the primary confusion with optional. The value() method and the std::bad_optional_access lurking inside it.

Seeing something like above that, a lot of C++ students, have been wondering what is the real worth of using std:: optional, at all.

Let me try and explain step by step

First the basics.

In other words, we must have the complete T, ad the moment of ‘marriage, with optional<T> :

We can use any type T as long as it is fully defined at the moment of usage.

The fact we have optional<T> instantiated with some type T, means there is a type ‘T’, declared and defined “somewhere” in the same program, but crucially in the same scope. And it has to exist in the same scope as its user aka optional<T> exists, at the moment of ‘marriage’ :

to fix that we must introduce T into the scope:

For each declared optional<T>, If the compile-time environment has both T and  optional<T> in the same scope at the point of declaration we will have no problems whatsoever.  Same applies for the usage of the instance of the particular defined optional type.

Above must compile and work as long as there is type T in the same scope as there was when the optional<T> was created.

And of course, with the auto keyword we do not have to know type the type T in advance.  Which is a rather big deal. Without auto one would need a separate version of magical_function , for each type returned.

The tool

For what it’s worth, I will give you one simple but very useful little utility, I am using always with optional’s.

With above I do not have to worry about (and code around ) the issue of ’empty’ optional’s.  Namely freshly minted optional will throw the exception if you ask it for a value and it has none yet. Behind that link, you will find an example on how to code around this. But who wants to retype this each time value is needed from optional.

In contrast to that, in my API, by design, the fallback value is default constructed type T. If user does not provide one.

Very comfortable “minuscule” API one might say.

No exception thrown. Especially welcome attribute in the embedded programming world. And now.

std::any

Is the troubled kid on the standard C++ block. Quite a few authors have tried to articulate the reason of it’s existence.  I personally can perhaps see its value in one and a half, situations.

First (the half) is where you have a limited number of types to pass internally using std::any.  And this is how you code around getting the value from std::any :

That is ugly as hell.  Not feasible for more than a handful of possible types your design will define. But then one must ask why not just using the union.  Sigh.

The second and last, and I think the only reasonable situation I can imagine is using std::any for serialization/de-serialization . I.e for sending object’s “over a wire”, through messaging middle-ware, for storing uniformly into the database and a such.

As you might imagine the above SQLite scenario is far from simple software. And very few teams or individuals spend their afternoons trying to write persistent objects management, in C++, when there is few very mature libraries for that, some even decades old and in widespread use.

Assuming we are all convinced we found a use case that might spare std::any from deprecation here is how I might use it.

I have to say, I do not use std::any, at the moment. I simply use optional<T>.

Just any hack

Let’s dive in head first, worry later.

The intent:  Have one factory method that returns an std::pair.  First of the pair is lambda function that can extract the value for the given type. Second of the pair is the instance of std::any.

Usage is simple:

auto[f, a] = make_any(324);

Above call makes a pair that contains std::any that contains integer 324. And an ‘f’ that is a function (lambda) that can extract from that instance of std::any (thea ) with no problems whatsoever.

auto optnl = f(a);
// we use here our new friend 'dbj::optitype'
assert( 324 ==  optitype( optnl )  );

As we can see above the “magical lambda” returns optional. Why not.  As any can be empty we use the optional to signal that result is empty. That is it equals std::nullopt.

This certainly works , as long as you keep that pair together, but then somewhat, it defeats the purpose of having std::any in the first place.

You decide.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.