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 students confusion with optional. The value() method and the std::bad_optional_access lurking inside it.

// give_me_my_int
int n;
     try {
        n = opt.value();
    } catch(const std::bad_optional_access& e) {
        std::cout << e.what() << '\n';
    }
  ctd::cout << n << '\n';

Seeing something like above, 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.

// from now on we do not need std::
using namespace std ;
// optional<T> is a template
template <typename T> class optional ;
// template is *not* a type
// template instance *is* a type
optional<int> ;
// we can declare type alias 
// as we almost always do with template instances
using optint_type = optional<int> ;

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

Type T, must be available at the moment of creation of optional<T>

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’ :

// compilation error, using non existent type
using string_opt = optional<std::string>;

to fix that we must introduce T into the scope:

#include <string>

using namespace std ;
// here we combine optional<T> and T
using string_opt = optional<string>;

For each declared optional<T>, If the compile-time environment has both T and  std::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.

// create empty instance
string_opt str_opt
// give it some value
str_opt = "Hello!";

// send it to some magical function far-far away
// compiles, links and runs 
// *only* ! if both T and std::optional<T> 
// are available 
template< typename O>
auto magical_function (O some_opt ) 
{
  // assert optional is not empty
  assert(some_opt.has_value()) 
  // compiler 'knows' the type of 
  // the value stored in optional
  auto opt_val = some_opt.value();
  return opt_val ;
}

// the usage
std::optional<int> optint{23} ;
// somewhere else, far away
int my_int = magical_function(optint) ;

Above must compile and work as long as there is type T in the same scope as there was when the optional<T>  template was defined  with concrete type, in this case optional<int> .

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 T inside optional<T>.

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.

// (c) 2019 by dbj.org -- free to use
template<typename T>
 inline T optival
  (std::optional<T> opt, T dflt_ = T{})
   noexcept // daring
  {
   return opt.value_or(dflt_);
  }

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.

// optional<int> op has come from somewhere 
// empty, into this scope
// returns 0 as that is int{}
auto v1 = optival(op) ;
// returns user defined 
// fallback val 43
// as op is still empty
auto v2 = optival(op, 43) ;

op = 123 ;
// returns 123, as op is not empty
// and fallback value is ignored
auto v2 = optival(op, 43) ;

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

The drawback is,  T has to have a default constructor. In C++ parlance it has to be trivially constructible.  We could stop that wrong usage at compile time, but that is out of scope of this article named primarily for C++ students.

Why not just using std::optional<T> dereference?

Allow me to simple copy/paste good explanation from here.

Let us recap and explain step by step. std::optional represents a value that may or may not be present, often replacing magic sentinel values (such as -1 or nullptr). It offers methods such as value(), which extract the T it contains and raises an exception if the the optional is empty. However, it also defines operator* and operator->. These methods also provide access to the underlying T, however

They do not check if the optional actually contains a value or not.

The following code for example, simply returns an uninitialized value:

#include <optional>

int f() {
    std::optional<int> x(std::nullopt);
    return *x;
}

If you use std::optional as a replacement for nullptr this can produce even more serious issues! Dereferencing a nullptr gives a segfault (which is not a security issue, except in older kernels). Dereferencing a nullopt however, gives you an uninitialized value as a pointer, which can be a serious security issue. While having a T* with an uninitialized value is also possible, these are much less common than dereferencing a pointer that was correctly initialized to nullptr.

And no, this doesn’t require you to be using raw pointers. You can get uninitialized/wild pointers with smart pointers as well:

#include <optional>
#include <memory>

std::unique_ptr<int> f() {
    std::optional<std::unique_ptr<int>> x(std::nullopt);
    return std::move(*x);
}

End of copy/paste. Phew, thanks god my optival is not using the dereferencing operator at all.

And finally.

The 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 :

// std::any a1 has come from somewhere into this scope
// what is inside it? 
//let's try and find out
// is is an int?
if (auto ptr = std::any_cast<int>(&a1)) {
  // yes it is an int
} else // is it a string?
if (auto ptr = std::any_cast<std::string>(&a1)) {
 // yes it is a string
} else {
 //what here?
}

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.

// application A
std::any a_("Hello!");
// 123 is the key
int key = 123 ;
store_any_to_sqlite(key,a_) ;

// application B
// has been somehow given the key:123
std::any a_ ;
read_any_from_sqlite(key,a_) ;
// will contain "Hello!"
auto rezult = std::any_cast<string>(a_);

As you might imagine the above SQLite scenario is far from simple. 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>. But here is one little utility that makes using std::any much more bearable.

Just any hack

Let’s dive in head first, worry later, what can possibly go wrong?

// (c) 2019 by dbj.org
// Licence CC BY SA 4.0
template<typename T>
inline decltype(auto) any_opt( T v_ )
{
// store the argument as the std::any
std::any any_{ v_ };
// the proxy function
// returning the T & v_
auto proxy_ = 
  [=]()->T const&
    {
       return v_;
    };
// on first call return the proxy and any 
// as the std::pair
   return make_pair(proxy_, any_);
}

The intent:  Have one function that returns two things,  std::pair :

  1. std::any  with the value inside
  2. proxy function returning the value, crucially with this original type.

This little number, does make std::any easier to handle or simply more palatable. This construct keeps std::any and its value type together.

Usage is simple:

// return proxy function 
// and std::any holding int 42
auto [proxy, anyval] = any_opt(42);

// get the value
int i42 = proxy();

// get the any holding the same int 42
std::any any_42 = anyval;

One can now use the any for what any is usually useful for.

   save_to_file( "filename.bin" , any_42);

This certainly works , as long as you keep that pair returned,  thus making std::any use cases much easier to code.

This might appear trivial but it is not. That pair does not have to be deconstructed upon return.  It can be copied or moved around, while holding its load.

// also testing move/copy of the solution
inline auto driver = [](auto va_pair) 
{
auto value_ = va_pair.first();
auto any_   = va_pair.second;
// here do something with the value 
// or with the any holding the same value
// for example storing it to the database
// finally move out
  return va_pair;
};

std::any is holding a memory block representing whatever it is given to keep.  Thus it can swap in/out that block while it has to move in and out of functions.

Now imagine the payload is not trivial. Just keep that pair.