Will Beason
Will Beason

Reputation: 3561

How to return an optional<T> without an if statement?

Suppose I have a function which finds and returns the minimum element of a vector. If the vector is empty, it should return an empty optional object. Is there a way for me to use the optional<T> constructor to avoid using either an if statement or the ternary operator?

With if statement:

optional<Foo> GetMinElement(vector<Foo> foos) {
  vector<Foo>::iterator min_foo = std::min_element(foos.begin(), foos.end());
  bool found_min_element = (min_foo != foos.end());
  if (found_min_element) {
    return *min_foo;
  } else {
    return nullopt;
  }
}

With ternary operator:

optional<Foo> GetMinElement(vector<Foo> foos) {
  vector<Foo>::iterator min_foo = std::min_element(foos.begin(), foos.end());
  bool found_min_element = (min_foo != foos.end());
  return found_min_element ? optional<Foo>(*min_foo) : optional<Foo>(nullopt);
}

Naively, I'd just like to be able to pass the output of an stl algorithm to the optional<T> constructor and have it handle the logic of checking for a null pointer. Is there some idiom for this?

Upvotes: 2

Views: 550

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

You have to remember that the end iterator is not a null pointer. It can even be a valid iterator if the range was a subrange.

I write my own range algorithms. They follow a different pattern than yours.

A range algorithm takes a class Range, does adl-enabled begin/end and runs an iterator algorithm. It then returns an optional iterator (empty if it would be end).

This is slightly different than yours, because mine does not return a copy of an element.

And they can be used (with the addition of make_range(start,finish)) everywhere the originals are.


If you dislike the syntax of both if and ?, these may help:

template<class T>
std::optional<std::decay_t<T>> maybe(bool engage, T&&t){
  if (!engage) return {};
  return std::forward<T>(t);
}

or even:

templace<class F>
std::optional<std::decay_t<std::result_of_t<F()>>>
perhaps(bool do_it, F&&f){
  if (!engage) return {};
  return std::forward<F>(f)();
}

which short-circuits like ? does.

namespace adl_helper{
  using std::begin; using std::end;
  template<class R> auto adl_begin(R&&)->decltype(begin(std::declval<R>())){
    return begin(std::forward<R>(r));
  }
  template<class R> auto adl_end(R&&)->decltype(end(std::declval<R>())){
    return end(std::forward<R>(r));
  }
}
using adl_helper::adl_begin;
using adl_helper::adl_end;

template<class R>using iterator_t=decltype(adl_begin(std::declval<R>()));

template<class R>
optional<iterator_t<R>> min_element(R&& r) {
  auto ret = std::min_element(adl_begin(r), adl_end(r));
  return maybe(ret!=adl_end(r), ret);
}

which I find is more useful than your version.

You do need to do an extra * sometimes, or use a helper.

Upvotes: 2

M.M
M.M

Reputation: 141628

optional does not seem to have a constructor that can sometimes yield an empty optional and sumetimes yield a full one.

Perhaps you could make a helper:

template<typename Iter>
optional<typename Iter::value_type> try_deref(Iter pos, Iter singular)
{
    if ( pos != singular )
        return *pos;

    return {};
}

Sample usage:

optional<Foo> GetMinElement(std::vector<Foo> const &foos)
{
    auto min_foo = std::min_element(foos.begin(), foos.end());
    return try_deref(min_foo, foos.end()); 
}

Upvotes: 2

Related Questions