Do C++11 Compilers Turn Local Variables into Rvalues When They Can During Code Optimization

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;

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.

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.

Are compilers clever enough to std::move variables going out of scope?

The compiler is not permitted to arbitrarily decide to transform an lvalue name into an rvalue to be moved from. It can only do so where the C++ standard permits it to do so. Such as in a return statement (and only when its return <identifier>;).

*to_be_filled = v; will always perform a copy. Even if it's the last statement that can access v, it is always a copy. Compilers aren't allowed to change that.

My understanding is that return v is O(1), since NRVO will (in effect) make v into an rvalue, which then makes use of std::vector's move-constructor.

That's not how it works. NRVO would eliminate the move/copy entirely. But the ability for return <identifier>; to be an rvalue is not an "optimization". It's actually a requirement that compilers treat them as rvalues.

Compilers have a choice about copy elision. Compilers don't have a choice about what return <identifier>; does. So the above will either not move at all (if NRVO happens) or will move the object.

Is there a subtle reason why not?

One reason this isn't allowed is because the location of a statement should not arbitrarily change what that statement is doing. See, return <identifier>; will always move from the identifier (if it's a local variable). It doesn't matter where it is in the function. By virtue of being a return statement, we know that if the return is executed, nothing after it will be executed.

That's not the case for arbitrary statements. The behavior of the expression *to_be_filled = v; should not change based on where it happens to be in code. You shouldn't be able to turn a move into a copy just because you add another line to the function.

Another reason is that arbitrary statements can get really complicated really quickly. return <identifier>; is very simple; it copies/moves the identifier to the return value and returns.

By contrast, what happens if you have a reference to v, and that gets used by to_be_filled somehow. Sure that can't happen in your case, but what about other, more complex cases? The last expression could conceivably read from a reference to a moved-from object.

It's a lot harder to do that in return <identifier>; cases.

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.

In what cases can't a modern day compiler apply the NRVO optimization for functions?

Both won't use RVO, because it's impossible. The problem is: && is still just a reference. The variable you're returning is not inside your function-local scope! So, without std::move, you'll copy, and with it you'll move. I would advice btw not to expect something per rvalue-reference, as long as you're not writing a move constructor/assignment operator or some perfect-forwarding template-code. Just take it by value. It has a small overhead in some cases, but it's really not going to be significant. And it makes code a lot more readable. And simpler, as the caller can either copy or move the arguments, and you don't have to provide additional const& overloads.



Related Topics



Leave a reply



Submit