Search code examples
c++stdc++17boost-optional

Temporary optional in the range-based for loop expression


Assume we have a function which returns std::optional<A>. Then what is a proper way of using the result in the range-based for loop? The easiest way does not work:

for (auto&& e : a().value()) {
                   // ^--- A&& is returned, so A is destructed
                   // before loop starts

This issue would not exist if we would had T optional::value() && instead of T&& optional::value() &&, but both STL and Boost define it in a second way.

What is a proper way of handling this situation? I don't like both solutions that I could think of (sandbox):

std::experimental::optional<A> a() {
  // ...
}

void ok1() {
  // ugly if type of A is huge
  for (auto&& e : A(a().value())) {
     // ...
  }
}

void ok2() {
  // extra variable is not used
  // if for some reason we are sure that we have a value
  // and we skip checks
  auto&& b = a();
  for (auto&& e : b.value()) {
    // ...
  }
}

// it may be that the best choice is to define
A aForced() {
    return A(a().value());
}

Solution

  • This solves your problem:

    template<class T>
    std::decay_t<T> copy_of(T&& t){
      return std::forward<T>(t);
    }
    
    template<class T, std::size_t N>
    void copy_of(T(&)[N])=delete;
    

    Then:

    for(auto&& x:copy_of(a().value()))
    

    The copy_of technique generally solves functions returning rvalue references being used on for(:) loops.


    An alternative is writing value_or_run(T&&, F&&f) which takes a lambda can also be useful. In the F you can do whatever you want, such as throw, and it returns a T not a T&&.

    Similarly, value_or.

    My personal optional used emplace syntax for value_or -- if the one you are using has that, then .value_or( throw_if_empty{} ), where throw_if_empty has an operator T() that throws the optional empty error.