When Is Explicit Move Needed for a Return Statement

When is explicit move needed for a return statement?

Update:

Explicit move should not be needed in modern compiler versions.

Core DR 1579 changed the rules so that the return value will be treated as an rvalue even when the types are not the same. GCC 5 implements the new rule, for C++11 as well as C++14.

Original answer:

This is not a limitation of unique_ptr, it's a limitation of the language, the same limitation applies to any return statement that calls a converting constructor taking an rvalue reference:

struct U { };

struct T {
T(U&&) { }
};

T f() {
U u;
return u; // error, cannot bind lvalue to U&&
}

This will not compile because [class.copy]/32 says:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

This means that the expression in a return statement can only be treated as an rvalue if it is eligible for copy/move elision (aka NRVO) but that is too restrictive because it means it only applies when the type is exactly the same, even though the variable is always going out of scope so it would be reasonable to always treat is as an rvalue (technically as an xvalue, an expiring value.)

This was recently suggested by Richard Smith (and previously by Xeo) and I think it's a very good idea.

Why explicit std::move is needed when returning compatible type?

The return statement in get_tuple() should be copy-initialized using the move-constructor, but since the type of the return expression and the return type don't match, the copy-constructor is chosen instead. There was a change made in C++14 where there is now an initial phase of overload resolution that treats the return statement as an rvalue when it is simply an automatic variable declared in the body.

The relevant wording can be found in [class.copy]/p32:

When the criteria for elision of a copy/move operation are met, [..], or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body [..], overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

So in C++14 all output should be coming from the move-constructor of A.

Trunk versions of clang and gcc already implement this change. To get the same behavior in C++11 mode you'll need to use an explicit std::move() in the return statement.

When should std::move be used on a function return value?

In the case of return std::move(foo); the move is superfluous because of 12.8/32:

When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed as
if the object were designated by an rvalue.

return foo; is a case of NRVO, so copy elision is permitted. foo is an lvalue. So the constructor selected for the "copy" from foo to the return value of meh is required to be the move constructor if one exists.

Adding move does have a potential effect, though: it prevents the move being elided, because return std::move(foo); is not eligible for NRVO.

As far as I know, 12.8/32 lays out the only conditions under which a copy from an lvalue can be replaced by a move. The compiler is not permitted in general to detect that an lvalue is unused after the copy (using DFA, say), and make the change on its own initiative. I'm assuming here that there's an observable difference between the two -- if the observable behavior is the same then the "as-if" rule applies.

So, to answer the question in the title, use std::move on a return value when you want it to be moved and it would not get moved anyway. That is:

  • you want it to be moved, and
  • it is an lvalue, and
  • it is not eligible for copy elision, and
  • it is not the name of a by-value function parameter.

Considering that this is quite fiddly and moves are usually cheap, you might like to say that in non-template code you can simplify this a bit. Use std::move when:

  • you want it to be moved, and
  • it is an lvalue, and
  • you can't be bothered worrying about it.

By following the simplified rules you sacrifice some move elision. For types like std::vector that are cheap to move you'll probably never notice (and if you do notice you can optimize). For types like std::array that are expensive to move, or for templates where you have no idea whether moves are cheap or not, you're more likely to be bothered worrying about it.

c++11 Return value optimization or move?

Use exclusively the first method:

Foo f()
{
Foo result;
mangle(result);
return result;
}

This will already allow the use of the move constructor, if one is available. In fact, a local variable can bind to an rvalue reference in a return statement precisely when copy elision is allowed.

Your second version actively prohibits copy elision. The first version is universally better.

Return Value Optimization: Explicit move or implicit?

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.

Constructor called on return statement

From [class.copy.elision]:

In the following copy-initialization contexts, a move operation might be used instead of a copy operation: If the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body [...]

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

In return result; we are precisely in the case mentioned in that paragraph - result is an id-expression naming an object with automatic storage duration declared in the body. So, we first perform overload resolution as if it were an rvalue.

Overload resolution will find two candidates: X(X const&) and X(X&&). The latter is preferred.

Now, what does it mean for overload resolution to fail? From [over.match]/3:

If a best viable function exists and is unique, overload resolution succeeds and produces it as the result. Otherwise overload resolution fails and the invocation is ill-formed.

X(X&&) is a unique, best viable function, so overload resolution succeeds. This copy elision context has one extra criteria for us, but we satisfy it as well because this candidate has as the type of its first parameter as (possibly cv-qualified) rvalue reference to X. Both boxes are checked, so we stop there. We do not go on to perform overload resolution again as an lvalue, we have already selected our candidate: the move constructor.

Once we selected it, the program fails because we're trying to call a deleted function. But only at that point, and not any earlier. Candidates are not excluded from overload sets for being deleted, otherwise deleting overloads wouldn't be nearly as useful.

This is clang bug 31025.

Explicitly calling return in a function or not

Question was: Why is not (explicitly) calling return faster or better, and thus preferable?

There is no statement in R documentation making such an assumption.

The main page ?'function' says:

function( arglist ) expr
return(value)

Is it faster without calling return?

Both function() and return() are primitive functions and the function() itself returns last evaluated value even without including return() function.

Calling return() as .Primitive('return') with that last value as an argument will do the same job but needs one call more. So that this (often) unnecessary .Primitive('return') call can draw additional resources.
Simple measurement however shows that the resulting difference is very small and thus can not be the reason for not using explicit return. The following plot is created from data selected this way:

bench_nor2 <- function(x,repeats) { system.time(rep(
# without explicit return
(function(x) vector(length=x,mode="numeric"))(x)
,repeats)) }

bench_ret2 <- function(x,repeats) { system.time(rep(
# with explicit return
(function(x) return(vector(length=x,mode="numeric")))(x)
,repeats)) }

maxlen <- 1000
reps <- 10000
along <- seq(from=1,to=maxlen,by=5)
ret <- sapply(along,FUN=bench_ret2,repeats=reps)
nor <- sapply(along,FUN=bench_nor2,repeats=reps)
res <- data.frame(N=along,ELAPSED_RET=ret["elapsed",],ELAPSED_NOR=nor["elapsed",])

# res object is then visualized
# R version 2.15

Function elapsed time comparison

The picture above may slightly difffer on your platform.
Based on measured data, the size of returned object is not causing any difference, the number of repeats (even if scaled up) makes just a very small difference, which in real word with real data and real algorithm could not be counted or make your script run faster.

Is it better without calling return?

Return is good tool for clearly designing "leaves" of code where the routine should end, jump out of the function and return value.

# here without calling .Primitive('return')
> (function() {10;20;30;40})()
[1] 40
# here with .Primitive('return')
> (function() {10;20;30;40;return(40)})()
[1] 40
# here return terminates flow
> (function() {10;20;return();30;40})()
NULL
> (function() {10;20;return(25);30;40})()
[1] 25
>

It depends on strategy and programming style of the programmer what style he use, he can use no return() as it is not required.

R core programmers uses both approaches ie. with and without explicit return() as it is possible to find in sources of 'base' functions.

Many times only return() is used (no argument) returning NULL in cases to conditially stop the function.

It is not clear if it is better or not as standard user or analyst using R can not see the real difference.

My opinion is that the question should be: Is there any danger in using explicit return coming from R implementation?

Or, maybe better, user writing function code should always ask: What is the effect in not using explicit return (or placing object to be returned as last leaf of code branch) in the function code?

Explicit move constructor

It is because returning a value is considered an implicit conversion.

Quoting from the C++11 standard:

6.6.3 The return statement

2 [...]

A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2). [...]

The conversion from the return expression to the temporary object to hold the return value is implicit. So just as this would result in an error

Foo f = Foo();   // Copy initialization, which means implicit conversion

having code as your example would also trigger a similar one.


Question 1: Why does the compiler need the copy-ctor of Foo? I expected the return value of bar to be constructed from the rvalue Foo() with move-ctor.

It is because Foo(Foo&&) is not a viable overload, because of the reasons above. The rules state that whenever the move constructor cannot be used, it is when the compiler shall then consider the copy constructor, which, in your case, is implicitly deleted due to the presence of a user-defined move constructor.

Question 2: Why the compiler does not need copy-ctor any more when I redeclare the move-ctor as implicit?

It is because your move constructor can be used now. Thus, the compiler can immediately use it without even considering the presence of the copy constructor.

Question 3: What does explicit keyword mean in the context of copy and move ctors as it definitely means something different from from the context of regular ctors.

IMHO, it doesn't make sense, and it only leads to problems, just as in your case.

Why is move constructor not picked when returning a local object of type derived from the function's return type?

[class.copy]/32 continues:

[...] if the type of the first parameter of the selected constructor
is not an rvalue reference to the object's type (possibly
cv-qualified), overload resolution is performed again, considering the
object as an lvalue.

The first overload resolution, treating d as an rvalue, selects Base::Base(Base&&). The type of the first parameter of the selected constructor is, however, not Derived&& but Base&&, so the result of that overload resolution is discarded and you perform overload resolution again, treating d as an lvalue. That second overload resolution selects the deleted copy constructor.



Related Topics



Leave a reply



Submit