c++c++11standardsrvoreturn-value-optimization

Why are the RVO requirements so restrictive?


Yet another "why must std::move prevent the (unnamed) return-value-optimization?" question:

Why does std::move prevent RVO? explains that the standard specifically requires that the function's declared return type must match the type of the expression in the return statement. That explains the behavior of conforming compilers; however, it does not explain the rationale for the restriction.

Why do the rules for RVO not make an exception for the case where the function's return type is T and the type of the return expression is T&&?

I also am aware that implementing such things in compilers doesn't come for free. I am suggesting only that such an exception be allowed but not required.

I also am aware that return std::move(...) is unnecessary since C++11 already requires that move semantics be used when the RVO can't be applied. Nevertheless, why not tolerate an explicit request for optimization instead of turning it into a pessimization?


(Aside: Why are the return-value-optimization and rvo tags not synonyms?)


Solution

  • auto foo() -> T&&;
    
    auto test() -> T
    {
        return foo();
    }
    

    You say in this case the RVO should be allowed to be applied. However consider this legal implementation of foo:

    T val;
    
    auto foo() -> T&&
    {
       return static_cast<T&&>(val); // because yes, it's legal
    }
    

    The moral: only with prvalues you know for sure you have a temporary and most important you know the exact lifetime of the temporary so you can elide its construction and destruction. But with xvalues (e.g. T&& return) you don't know if that is indeed bound to a temporary, you don't know when that value was created and when it goes out of scope or even if you know you can't change it's construction and destruction point like the above example.

    I'm not sure that I fully understand. If RVO were allowed to be applied to test(), why would that be worse than if test did: T temp = foo(); return temp; which would allow NRVO?

    It's not that it is worse. It is just not possible. With your example temp is a local variable in the function where you want to apply NRVO i.e. test. As such it is an object fully "known" in the context of test, its lifetime is known, the normal point of ctor and dtor is known. So instead of creating the temp variable in the stack frame of test it is created in the stack frame of the caller. This means there is no copy of the object from the stack frame of test to the stack frame of the caller. Also please see that in this example foo() is completely irrelevant. It could have been anything in the initialization of temp:

    auto test() -> T
    {
        T temp = /*whatever*/;
    
        return temp; // NRVO allowed
    }
    

    But with return foo() you can't elide the copy simply because you can't know to what object the return reference binds to. It can be a reference to any object.