Return value optimization (RVO) is an optimization technique involving copy elision, which eliminates the temporary object created to hold a function\'s return value in cert
I am used to optimizations being constrained such that they cannot change observable behaviour.
This is correct. As a general rule -- known as the as-if rule -- compilers can change code if the change is not observable.
This restriction does not seem to apply to RVO.
Yes. The clause quoted in the OP gives an exception to the as-if rule and allows copy construction to be omitted, even when it has side effects. Notice that the RVO is just one case of copy-elision (the first bullet point in C++11 12.8/31).
Do I ever need to worry about the side effects mentioned in the standard?
If the copy constructor has side effects such that copy elision when performed causes a problem, then you should reconsider the design. If this is not your code, you should probably consider a better alternative.
What do I as a programmer need to do (or not do) to allow this optimization to be performed?
Basically, if possible, return a local variable (or temporary) with the same cv unqualified type as the function return type. This allows RVO but doens't enforce it (the compiler might not perform RVO).
For example, does the following prohibit the use of copy elision (due to the move):
// notice that I fixed the OP's example by adding
std::vector foo(int bar){
std::vector quux(bar, 0);
return std::move(quux);
}
Yes, it does because you're not returning the name of a local variable. This
std::vector foo(int bar){
std::vector quux(bar,0);
return quux;
}
allows RVO. One might be worried that if RVO is not performed then moving is better than coping (which would explain the use of std::move above). Don't worry about that. All major compilers will do the RVO here (at least in release build). Even if a compiler doesn't do RVO but the conditions for RVO are met then it will try to do a move rather than a copy. In summary, using std::move above will certainly make a move. Not using it will likely neither copy nor move anything and, in the worst (unlikely) case, will move.
(Update: As haohaolee's pointed out (see comments), the following paragraphs are not correct. However, I leave them here because they suggest an idea that might work for classes that don't have a constructor taking a std::initializer_list (see the reference at the bottom). For std::vector, haohaolee found a workaround.)
In this example you can force the RVO (strict speaking this is no longer RVO but let's keep calling this way for simplicity) by returning a braced-init-list from which the return type can be created:
std::vector foo(int bar){
return {bar, 0}; // <-- This doesn't work. Next line shows a workaround:
// return {bar, 0.0, std::vector::allocator_type{}};
}
See this post and R. Martinho Fernandes's brilliant answer.
Be carefull! Have the return type been std::vector the last code above would have a different behavior from the original. (This is another story.)