What’s wrong with you, std::optional?

“He Who Laughs Last Is At 300 Baud”, is possibly a long-forgotten joke, but sometimes C++ standard is like using a 300 baud modem, discovering “innovations” tens of years after other less committee-centric languages discover and apply them.

Let’s take the std::optional which tries to mimic the Option monad available in other languages. Since 1990 there has been a resurgence of functional programming languages in the mainstream – Haskell (1990), and Scala (2004) just to name two that have Option since their first version.

C++ programmers had to wait until C++17 to receive std::optional. Given the long wait, you’d expect that std::optional works like a clockwork of perfection, wouldn’t you? Improving based on what other languages had, right?. Well… er… not exactly.

The main failure (aside from the long wait) is failing to recognize the monadic nature of std::optional and interpreting the optionality as a glorified pointer.

The std::optional interface is the pointer interface:

std::optional<int> opt{3};   // opt with value;
std::optional<int> no{};     // opt with no value

if( opt )  // check if the option has a value
{
  std::cout << *opt << '\n'; // dereference to access the value
}

Insisting on the null pointer paradigm, even after the null inventor claimed it to be a one billion-dollar mistake, is beyond my understanding. You forget the check and you get an exception, maybe a modest improvement over the null pointer dereferencing (possibly it is a not-so-modest improvement, since dereferencing a null pointer is undefined behavior, another twisted contraption of the C++ Language).

Other languages indeed allow the same (wrong) approach:

val opt = Some(3)
val no = None

if( opt.isDefined ) {
  println( opt.get )
}

But then they provide also a monadic interface –

val result = opt.map( _*3 ) // if opt has a value, then result is Some( 3*opt.get ), otherwise None

opt match {
  case Some(x) => println( s"x=$x" )
  case None => prntln( "no value" )
}

As you can see the map function is safe (no risk to access an undefined value) and is very concise. The only drawback is that the result is enclosed in an Option, that could, sometimes, cause a sort of avalanche effect. Pattern matching is another powerful tool that goes hand in hand with ADT (Some and None are subclasses of Option) providing an extremely expressive construct.

Note that even failing to recognize the monad in std::optional, the committee could have provided easy and effective access even if imperative, by considering std::optional as a sequence of one or zero items:

for( n : opt )
{
    std::cout << n << '\n';
}

By simply adding begin()/end() pair and a suitable iterator, std::optional could have worked like this, which is pretty clear and convenient.

For these reasons, I set out to develop my own Option template, with the goal of offering monadic and pattern-matching facilities as much as possible in the clumsy C++ ecosystem.

One thought on “What’s wrong with you, std::optional?

Leave a Reply

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