Temporary optional in the range-based for loop expression
Asked Answered
A

1

7

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());
}
Amygdaloid answered 4/6, 2016 at 23:13 Comment(2)
I actually wrote a whole post complaining about this exact behavior, and I talk about various options for solving it in user defined types, maybe you'd find it interesting: nirfriedman.com/2016/01/18/….Swirsky
@NirFriedman I never thought of the same kind of problems with std::get & co, thanks! And FYI: the code I am discussing in this question is live, so your post is really practical. I don't understand why there is no std::copy_of as one from the @yakk's answer, at least it would be a good way to make people pay attention to the problem.Amygdaloid
B
2

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.

Bellbella answered 4/6, 2016 at 23:28 Comment(3)
Sometime disengaged optional would mean an error somewhere, and an exception would be just fine. Actually, defining T extract(optional<T>&& o) appeals to me most of everything, and it is a little strange that there is nothing standard for that.Amygdaloid
@DmitryPanteleev Ah, I forgot .value() throws.Bellbella
As it was pointed in another comment, this is not-optional-only problem, so copy_of could be indeed useful. throw_if_empty is cool, but it may be not clear for someone new why is it used instead .value()Amygdaloid

© 2022 - 2024 — McMap. All rights reserved.