It can but it's not exactly safe to use outside of this intent, please take care using this.
You can use rvalues to completely elide construction, but this means your intermediate functions (ie bar
) have to pass and accept reference parameters. It's not hard to stick to just rvalue references so something like this is possible:
class A { /* ... */ };
A && bar (A && a) {return std::move(a);}
A a1 = A();
A a2 = bar( A() );
A a3 = bar( bar( A() ) );
Rule of thumb: if you're passing by value, you can only remove spurious intermediate object instances via optimization, because the compiler has to inspect usage of the instance and re-arrange. However with references we outright declare we require an object that's already available, and rvalues extend this to temporary objects.
The "gotcha" with reference passing like this is if you pass a reference to an object who's scope expired you're in trouble. (eg, returning an rvalue reference to a local variable, no better than a regular reference but that case is likely to be flagged by the compiler whereas rvalues may not).
A && bad_valid_return() {
A temp;
return std::move(temp);
}
A a4 = bad_valid_return(); // not ok, object is destroyed once we return!
A good_valid_return() {
A temp;
return std::move(temp);
// better but we can only remove the result's construction via optimize
}
For what it's worth you can also do this:
A && a5 = good_valid_return();
a5
is in essence a regular variable (it's type will be A&
, virtually identical to A a5
when used). It will hold the actual temporary that is returned and as consequence the temporary is constructed in place. This can even get around private object members - a5
assignment would work even if operator=
and the constructor were private (and friended to good_valid_return
).
Regarding bar
This will almost never be allowed, in the general case, to completely avoid move operators. Consider every "by value" value passing as a construction barrier - values crossing that point must result with a new container except where it's "obvious" that only one object will ever be needed.
These "obvious" cases are the return-value-optimization cases. As separate code units, functions cannot know how their values will be used, so functions trading in "by value" passing must assume a new object will be needed.
The exception to this is inlining - the function code is optimized where it is referred to, essentially removing the function call altogether - I expect that to be the only time your (1) case will be optimized away from any extra objects / moves / copies without using rvalue references to directly tell the compiler that that's what you intend to happen.
It does look like that's what can happen in (1) but you would need at least -O3
optimization with GCC for example, for that to happen. (maybe O2 but I wouldn't think so)