I agree. If we would start from scratch we probably should have the following semantic:
T t = wait(f); // moves result of f into t
const T& t = wait(f); // takes a reference into the future
The problem is, that currently the syntax for the first one has the semantics of the second one. So changing this (if even possible) will require us to refactor quite some code…
If this were standard C++, wait() would be a function that would return a prvalue that would always select the T(&&) constructor overload when present.
When you say “move the result”, is that removing it from f such that f.get() would no longer work? If so, that’s maybe a little different than the function foo example, which isn’t obviously modifying a parameter to the function.
This is the thing we need to figure out. I would say the following would be the best behavior (as in closest to what other C++ code would probably work):
T t = wait(someActor()); // moves result of someActor into t after completion
T t = wait(std::move(f)); // moves result of f into t after completion
T const& t = wait(f); // gets a reference into the future
T const& t = wait(someActor()); // compilation error
T t = wait(f); // copies the result of the future f when it becomes available
Now there are a few problems:
The actor compiler can’t do type deduction. So implementing this would be difficult at best.
Future<T> doesn’t own the value - so above is probably still wrong.
Instead having a consume method (as Steve suggested) is probably cleaner. IMHO the semantics should then be: only allow to call consume if SAV<T>::futures == 1 && SAV<T>::promised == 0 for the underlying sav.