Avoiding unnecessary copies from movable futures

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…

That looks like it should copy the result of f into t to me

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.

Assuming that there wasn’t copy elision going on.

Really? How so? If wait would be a normal function wouldn’t you expect the result of wait to be moved into the result? Say the following:

std::string foo(...);

// later in a function
std::string str = foo(/* some params */);

Would you expect a copy above? Or do you really think this syntax looks natural:

std::string&& str = foo(/* some params */);

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.

1 Like

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:

  1. The actor compiler can’t do type deduction. So implementing this would be difficult at best.
  2. 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.