1

I tried to iterate over an std::optional:

for (auto x : optionalValue)
{
    ...
}

With the expectation of it doing nothing if optionalValue is empty but doing one iteration if there is a value within, like it would work in Haskell (which arguably made std::optional trendy):

forM optionalValue
( \x ->
    ...
)

Why can't I iterate an optional? Is there another more standard C++ method to do this?

yairchu
  • 23,680
  • 7
  • 69
  • 109
  • 2
    `if (optionalValue) { ....}` ? – 463035818_is_not_an_ai Jun 30 '22 at 08:32
  • 2
    What about `if(optionalValue)` like it is intended to use :D – RoQuOTriX Jun 30 '22 at 08:32
  • 1
    you should post a [mcve] and the error message. The error message should contain enough information to answer the "why not?". And reading the docs can answer the second question – 463035818_is_not_an_ai Jun 30 '22 at 08:33
  • 1
    sorry, but the question reads like "I wish C++ would work differently. Why does it not work the way I wish?" – 463035818_is_not_an_ai Jun 30 '22 at 08:35
  • @RoQuOTriX Let's assume it was `if (auto optionalValue = computation())`. The fact that I need to dereference `optionalValue` after this feels like an extra mental burden (I have to remind myself that I don't need to check again whether it has a value). – yairchu Jun 30 '22 at 08:37
  • 2
    It looks like what you need is [`views::maybe`](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1255r7.html), but it shouldn't be C++23, C++26 might adopt it. – 康桓瑋 Jun 30 '22 at 08:38
  • fwiw a `std::vector` can also hold either 1 or 0 elements and you can iterate it (though in contrast to `std::optional` it uses dynamic allocations) – 463035818_is_not_an_ai Jun 30 '22 at 09:07
  • 1
    @yairchu I just updated my answer to include another way to get even closer to what you wanted (see "Alternative 2"). But do consider if you actually need this. – bitmask Jun 30 '22 at 09:12

1 Answers1

8

std::optional does not have a begin() and end() pair. So you cannot range-based-for over it. Instead just use an if conditional.

See Alternative 2 for the closest thing to what you want to do.

Edit: If you have a temporary from a call result you don't have to check it explicitly:

if (auto const opt = function_call()) {
  do_smth(*opt);
}

The check for static_cast<bool>(opt) is done implicitly by if.


Alternative 1

Another alternative is to not use an std::optional<T> but std::variant<std::monostate, T>. You can then either use the overloaded idiom or create a custom type to handle the monostate:

template <typename F>
struct MaybeDo {
  F f;

  void operator()(std::monostate) const {}
  template <typename T>
  void operator()(T const& t) const { f(t); }
};

which will allow you to visit the value with some function:

std::variant<std::monostate, int> const opt = 7;
std::visit(MaybeDo{[](int i) { std::cout << i << "\n"; }}, opt);

Alternative 2

You can also wrap optional in a thing that allows you to iterate over it. Idea:

template <typename T>
struct IterateOpt {
  std::optional<T> const& opt;
  
  struct It {
    std::optional<T> const* p;
    It& operator++() {
      p = nullptr;
      return *this;
    }
    It operator++(int) {
      return It{nullptr};
    }
    auto const& operator*() const { **p; }
  };
  auto begin() const {
    if (opt) return It{&opt};
    else end();
  }
  auto end() const {
    return It{nullptr};
  }
};

This is a crude sketch of how to do this and probably requires some love to work on different situations (like a non-const optional).

You can use this to "iterate" over the optional:

for (auto const& v: IterateOpt{function_call()}) {
  do_smth(v);
)
bitmask
  • 32,434
  • 14
  • 99
  • 159