Can Returning a Local Variable by Value in C++11/14 Result in the Return Value Being Constructed by Rvalue When No Copy/Move Is Involved

Can returning a local variable by value in C++11/14 result in the return value being constructed by rvalue when no copy/move is involved?

The rule for this situation changed between 2011 and 2014. The compiler should now treat localB as an rvalue.

The applicable rule for return statements is found in §12.8 [class.copy]/p32, which reads in C++14 (quoting N3936, emphasis mine):

When the criteria for elision of a copy/move operation are met, but
not for an exception-declaration, and the object to be copied is
designated by an lvalue, 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 or
parameter-declaration-clause of the innermost enclosing function or lambda-expression
, 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.

The bolded clause was added by CWG issue 1579, expressly to require the converting move constructor A::A(B&&) to be called here. This is implemented in GCC 5 and Clang 3.9.

Back in 2011, this "try rvalue first" rule was closely tied to the criteria for copy elision (quoting N3337):

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.

Since copy elision necessarily requires the two to have the same type, this paragraph didn't apply, and the compiler had to use the A::A(B&) constructor.

Note that as CWG 1579 is considered a DR against C++11, compilers should implement its resolution even in C++11 mode.

Returning local variable of different type is considered as rvalue for overload resolution

The rules for this changed between C++11 and C++14.

C++11 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.

C++14 12.8/32:

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, 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 or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

So your example is ill-formed in C++11, but well-formed in C++14.

Automatic move from local variables when variable is rvalue reference

Is this something which haven't been implemented yet in compilers?

Not implemented yet (look for P1825), you understand the rule correctly. But let's go through it to be sure. The specific standard text here is in [class.copy.elision]/3:

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
  • if the operand of a throw-expression is a [...]

rf is an rvalue reference (check) to Foo (non-volatile object type, check), which makes it an implicitly movable entity. In return rf;, that's an id-expression that names an implicitly movable entity (check) declared in the body of the function (check). So, we move instead of copy.

Return a local object rvalue reference,right or wrong?

Returning a reference to a local automatic variable is always wrong. The variable will be destroyed when the function returns, so any use of the reference will give undefined behaviour.

It makes no difference whether it's an rvalue or lvalue reference.

Can we use the return value optimization when possible and fall back on move, not copy, semantics when not?

When the expression in the return statement is a non-volatile automatic duration object, and not a function or catch-clause parameter, with the same cv-unqualified type as the function return type, the resulting copy/move is eligible for copy elision. The standard also goes on to say that, if the only reason copy elision was forbidden was that the source object was a function parameter, and if the compiler is unable to elide a copy, the overload resolution for the copy should be done as if the expression was an rvalue. Thus, it would prefer the move constructor.

OTOH, since you are using the ternary expression, none of the conditions hold and you are stuck with a regular copy. Changing your code to

if(b)
return x;
return y;

calls the move constructor.

Note that there is a distinction between RVO and copy elision - copy elision is what the standard allows, while RVO is a technique commonly used to elide copies in a subset of the cases where the standard allows copy elision.

Are return values going to be passed by rvalue reference in c++0x?

The rule is the following

  • If the compiler can do RVO, then it is allowed to do it, and no copy and no move is made.
  • Otherwise, the appropriate constructor is taken.

Like you say, the temporary is an rvalue, and thus the move constructor is selected, because of a rule in 13.3.3.2/3, which says that a rvalue reference binds to an rvalue better than an lvalue reference. In deciding whether to use the move or the copy constructor, overload resolution will therefor prefer the move constructor.

The rule that the compiler is allowed to perform RVO is written at 12.8/15.

Returning an rvalue versus returning a local object by value

In both cases you create a local variable in createBoVector(). reusable is then copy-constructed from that local object, before said local goes out of scope and is destroyed.

The only difference is that one time you make the local variable explicit (boVector bv), one time you don't. All that matters here is the return type of boVector.

It may be that compilers can optimize your simple example to avoid the copy, e.g. by inlining createBoVector(), but you can't really influence that or rely on that. And, unless your objects are prohibitively expensive to copy (unlikely), you shouldn't worry about it either.

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.

Do C++11 compilers turn local variables into rvalues when they can during code optimization?

The compiler cannot break the "as-if" rule in this case. But you can use std::move to achieve the desired effect:

object3 a(x);
object2 b(std::move(a));
object1 c(std::move(b));
return c;


Related Topics



Leave a reply



Submit