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

2.1k views Asked by At

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();
}
4

There are 4 answers

0
Vittorio Romeo On BEST ANSWER

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.

2
Ghasem Ramezani On

I think you could just return what you want and catch the exceptions in root function, something like this:

auto  get_size_t(const bool state)
{
 return state ? std::optional<size_t>(std::rand()) : std::nullopt;
}

auto  get_string_size_t(const bool state)
{
 return std::optional<std::string>(std::to_string(get_size_t(state).value()));
}

void  f()
try
{
 std::clog << get_string_size_t(true).value() << std::endl;
 std::clog << get_string_size_t(false).value() << std::endl;
}
catch (...)
{
}

0
Caleth On

boost::optional, which is what std::optional is based on, has a member map that does this.

E.g.

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

auto getAuthor(const std::string &title) const -> boost::optional<Author>
{
   return getBook(title).map(std::mem_fn(&Book::getAuthor));
}
0
Leontyev Georgiy On

I think, Null Object pattern is required here for the job instead of std::optional.

Yeah, it resembles std::optional a bit, but with a different semantics: When any method (like getAuthor) is called on an object, if method's result is valid, valid object is returned. If not, null object is returned instead.

Not too much time ago I worked on a XML-like tree implementation, it was something like that:

auto val = object.node("root").node("branch_1").node("subbranch_1").toInt();
if (val) process(val.asInt());

On every step of node() call, if specific node wasn't found, invalid node is returned. When it is called on an invalid node, invalid node is returned.

AFAIK, nothing like that is currently implemented in STL.

EDIT: for this to effectively work, you need all instances of books, authors, chapters, and other stuff to be derived from some base class, one that null object will be of.

EDIT: This isn't the best solution for all cases, probably, this is too complicated to implement in yours.