3

I have a piece of generic code that, when instantiated, boils down to:

struct A{...};

A f1(){
    A ret;
    std::pair<A&, int> p(ret, 1);
    p = g(); // g returns a pair<A, int>, but I am not interested in g
    return ret; // RVO :)
};

As far as I understand, this will work with RVO.

The question is, would this other code return the object of type A with RVO?

A f2(){
    std::pair<A, int> p;
    p = g();
    return p.first; // RVO ?
};

I understand that since the return object is obscured, it will not do RVO or that the compiler may not be able to pick the "optimization". But I don't see a fundamental reason why it wouldn't be possible, in other words, I think for consistency, it should do RVO (which I want).

The reason I ask, it because I think f2 is more elegant code than f1.

Should this last version do RVO? If so, is it compiler dependent?


My crude test with gcc 8.1 gives that RVO doesn't work in f2 and f3 below:

struct A{
    double something;
    A() = default;
    A(A const& other) : something(other.something){std::cerr << "cc" << '\n';}
    A(A&& other) : something(other.something){std::cerr << "mc" << '\n';}
};

A f1(){
    A ret;
    std::pair<A&, int> p(ret, 1);
    p.first.something = 5.;
    return ret; // RVO :)
};

A f2(){
    std::pair<A, int> p;//(ret, 1);
    p.first.something = 5.;
    return p.first; // no RVO :(
};

A f3(){
    std::pair<A, int> p;//(ret, 1);
    p.first.something = 5.;
    return std::move(p).first; // no RVO :(
};


int main(){
    A a1 = f1(); // prints nothing, RVO
    A a2 = f2(); // prints "cc"; no RVO! Why?
    A a3 = f3(); // prints "mc" (no much gain in this case); still no RVO!
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
alfC
  • 14,261
  • 4
  • 67
  • 118

1 Answers1

5

For RVO to work, the return value needs to be instantiated in the storage where the caller expects to find it. Perhaps this is a register whose name is specified by the calling convention, or perhaps it is on the stack.

std::pair<A, int> is essentially:

struct foo {
    A first;
    int second;
};

Now, if the return value needs to be stored at a specific place with sizeof(A), but the pair has a larger size, the pair cannot possibly be stored there. The only way RVO could still be made to work is if the callee knew that the sizeof(int) bytes following the return value were allowed to be clobbered while the function was executing. But the language probably shouldn't require RVO in such a case, because it might not be implementable in every calling convention.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Are these rules coverd by the Standard? It makes perfect sence, but I am just curious... – Daniel Langr Jun 10 '18 at 03:17
  • 1
    The C++ standard does not typically cover implementation details, but it is written with the knowledge that it is implementable (often with existing implementations as proof). The RVO you are hoping for is probably not implementable on all platforms. – John Zwinck Jun 10 '18 at 03:24
  • It is understandable. I guess I can add the case refinement that the returned element is the first member of the class. So it can be at the beginning of the stack. – alfC Jun 10 '18 at 06:49