Return Value Optimization: Explicit move or implicit?

281 views Asked by At

I have a function like this, do i have to explicitly use move here or it is implicit?

std::vector<int> makeVector();
std::vector<int> makeVector2();

std::optional<std::vector<int>> getVectOr(int i) {
  if(i==1) {
    std::vector<int> v = makeVector();
    return std::move(v);
  }
  else if(i==2) {
    std::vector<int> v2 = makeVector2();
    return std::move(v2);
  }
  return std::nullopt;
}

2

There are 2 answers

1
Sam Varshavchik On BEST ANSWER

It doesn't matter whether you use std::move or not. No return value optimization will take place here. There are several requirements for RVO to take place.

One of the requirements for return value optimization is that the value being returned must be the same type as what the function returns.

std::optional<std::vector<int>> getVectOr(int i)

Your function returns std::optional<std::vector<int>>, so only a copy of a temporary of the same type will get elided. In the two return statements in question here, both temporaries are std::vector<int>s which are, of course, not the same type, so RVO ain't happening.

No matter what happens, you're returning std::optional<std::vector<int>>. That's an absolute requirement here. No exceptions. But your adventure to return something from this function always starts with std::vector<int>. No matter what you try, you can't turn it into a completely different type. Something will have to get constructed somewhere along the way. No return value optimization.

But having said that: there are also move semantics that come here into play. If the stars get fortunately aligned for you (and this is very likely) move semantics will allow for everything to happen without copying the contents of the large vector around. So, although no return value optimization takes place, you may win the lottery and have everything to happen without shuffling the actual contents of the vector across all your RAM. You can use your debugger, yourself, to confirm or deny whether you've won the lottery on that account.

You may also have a possibility of the other type of RVO, namely returning a non-volatile auto-scoped object from the function:

std::optional<std::vector<int>> getVectOr(int i) {

    std::optional<std::vector<int>> ret;

    // Some code

    return ret;
 }

It's also possible for return value optimization to take place here as well, it is optional but not mandatory.

0
walnut On

In addition to what has already been said:

Using std::move in the return statement prohibits return value optimization. Named return value optimization is only allowed if the return statement's operand is the name of an automatic non-volatile storage variable declared in the function body and if its type equals (up to cv qualification) the return type.

std::move(v2) does not qualify for this. It does not simply name a variable.

Named return value optimization is never mandatory either. It is optional and up to the compiler whether it will perform it (even in C++17 which made some copy elision mandatory).

However, if return value optimization is not done, then generally the return value will be moved automatically. return statements have special behavior and if the operand directly names a variable with similar conditions as above, then overload resolution will be done as if the return value initializer was an rvalue expression (even if it isn't), so that move constructors will be considered. This automatic move is done whether or not the type of the variable referred to in the return statement is the same as the return type, so it applies to your example as well.

There is no need to use std::move explicitly and it is a pessimization in some cases (although not yours specifically) as explained above. So just use:

std::optional<std::vector<int>> getVectOr(int i) {
  if(i==1) {
    std::vector<int> v = makeVector();
    return v;
  }
  else if(i==2) {
    std::vector<int> v2 = makeVector2();
    return v2;
  }
  return std::nullopt;
}