Search code examples
c++stdoptional

Converting one std::optional to another std::optional


I have a method that returns an optional struct, like this:

auto getBook(const std::string &title) const -> std::optional<Book>;

I want to call this method in another method that returns the optional author. Problem is that the implementation should always check whether the optional returned by getBook is filled in before a method can be called, like this:

auto getAuthor(const std::string &title) const -> std::optional<Author>
{
   const auto optBook = getBook(title);
   if (optBook.has_value)
      return optBook->getAuthor();
   else
      return std::nullopt;
}

Is there a way to write this in a shorter way, so that if the optional is filled the method is called, but if the optional is empty, std::nullopt is returned. Something like this (I know this currently doesn't work but you get my point):

auto getAuthor(const std::string &title) const -> std::optional<Author>
{
   return getBook(title).getAuthor();
}

Solution

  • You can generalize this pattern by creating a map function, that takes an optional o and a function f, and returns the result of f(*o) if o.has_value() == true:

    template <typename O, typename F>
    auto map(O&& o, F&& f) -> std::optional<decltype(f(*std::forward<O>(o)))>
    {
        if (!o.has_value()) 
        {
            return {std::nullopt};
        }
    
        return {f(*std::forward<O>(o))};
    }
    

    You can then define getAuthor as:

    auto getAuthor(const std::string& title) -> std::optional<Author>
    {
        return map(getBook(title), [](Book b)
        {
            return b.author;
        });
    }
    

    live example on godbolt.org


    I made a library for these sort of operations, called scelta. With my library, you can write:

    auto getBook(const std::string& title) -> std::optional<Book>;
    auto getAuthor(const std::optional<Book>& title) -> std::optional<Author>;
    
    using namespace scelta::infix;
    std::optional<Author> a = getBook("...") | map(getAuthor);
    

    See "Monadic Optional Operations" for more info.