c++c++17option-typestdoptional

Chain Optionals in C++


How to avoid nested if statements with chained optionals in C++?

For example, if type A contains an std::optional<B> b and type B an std::optional<C> c, I would like to be able to write something like:

const auto v = if_exists(if_exists(a->b)->c);

And v would get the value from c or an empty optional if either b or c are empty optionals.

I think this would be nicer that nested ifs like this:

if (a->b) {
 const auto b = *(a->b);
 if (b->c) {
  const auto c = *(b->c);
 }
}

The following question seems to go in this direction but I am not sure how to adapt it to my use-case: Haskell style "Maybe" type & *chaining* in C++11


Solution

  • You can do something like this (pseudocode-ish; link to buildable code is provided below):

    // wrap std::optional for chaining
    template <class T> class Maybe {
      std::optional<T> t;
    
      // ... constructors etc
    
      // Maybe chaining 
      // If A has a member named m of type M, 
      // then Maybe<A>.fetch(&A::m) returns a Maybe<M>
    
      template <class M>
      Maybe<M> fetch(M T::*mem_ptr) {
         return (bool(t)) ? Maybe<M>((*t).*mem_ptr) : Maybe<M>() ;
      }
    
      // Maybe chaining special case 
      // If A has a member named m, which is itself a Maybe<M>,
      // then return it without wrapping it in an additional Maybe
    
      template <class M>
      Maybe<M> fetch(Maybe<M> T::*mem_ptr) {
         return (bool(t)) ? ((*t).*mem_ptr) : Maybe<M>() ;
      }
    
    };
    

    Now if you have this:

     struct C { int d ; }
     struct B { C c; }
     struct A { B b; }
     A a;
     Maybe<A> ma;
    

    and you can do this

     int d = a.b.c.d;
    

    you cannot do the same with ma, but you can use the next best thing, namely:

     Maybe<int> md = ma.fetch(&A::b).fetch(&B::c).fetch(&C::d);
    

    And you can still use this if you Maybe-ify any or all struct members above:

     struct C { Maybe<int> d ; }
     struct B { Maybe<C> c; }
     struct A { Maybe<B> b; }
    

    Live example (not production quality but it builds).