Consider the following:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Will p.first
be copied, moved or RVO-ed?
Consider the following:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Will p.first
be copied, moved or RVO-ed?
I've found in Visual Studio 2010 and in gcc-5.1 RVO is not applied (see for example http://coliru.stacked-crooked.com/a/17666dd9e532da76).
The relevant section of the standard is 12.8.31.1 [class.copy]. It states that copy elision is permitted (my highlighting):
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler ([except.handle])) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
Since p.first
is not the name of an object, RVO is prohibited.
Just to add a little more fuel, how would this function if RVO were in play? The caller has put an instance of A
somewhere in memory and then calls foo
to assign to it (even better, let's assume that that A
was a part of a larger struct, and let's assume that it is correctly aligned such that the next member of the struct is immediately after that instance of A
). Assuming RVO were in play, the first
portion of p
is located where the caller wanted it, but where does the int
that is second
get placed? It has to go right after the instance of A
in order to keep the pair
functioning correctly, but at the source location, there's some other member right after that instance of A
.
I would expect that RVO would not be happening in this place as you are only returning a portion of a larger object. A move could happen as first
would have to be left in a destructible state.
@atkins got here first with the answer. Just adding this little test program which you may find useful in future when tracking move/assign behaviour.
#include <iostream>
#include <string>
using namespace std::string_literals;
struct A {
A()
: history("created")
{
}
A(A&& r)
: history("move-constructed,"s + r.history)
{
r.history = "zombie: was "s + r.history;
}
A(const A& r)
: history("copied from: " + r.history)
{
}
~A() {
history = "destroyed,"s + history;
std::cout << history << std::endl;
}
A& operator=(A&& r) {
history = "move-assigned from " + r.history + " (was "s + history + ")"s;
r.history = "zombie: was "s + r.history;
return *this;
}
A& operator=(const A&r ) {
history = "copied from " + r.history;
return *this;
}
std::string history;
};
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto main() -> int
{
auto a = foo();
return 0;
}
example output:
destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created
Consider following code:
struct A {};
struct B {};
struct C { B c[100000]; };
A callee()
{
struct S
{
A a;
C c;
} s;
return s.a;
}
void caller()
{
A a = callee();
// here should lie free unused spacer of size B[100000]
B b;
}
"Partial" RVO should result in excessive stack usage bloating in caller, because (I think) S
can be constructed only entirely in caller stack frame.
Another issue is ~S()
behaviour:
// a.hpp
struct A {};
struct B {};
struct C { A a; B b; ~C(); };
// a.cpp
#include "a.hpp"
~C() { /* ... */; }
// main.cpp
#include "a.hpp"
A callee()
{
C c;
return c.a;
} // How to destruct c partially, having the user defined ~C() in another TU?
// Even if destructor is inline and its body is visible,
// how to automatically change its logic properly?
// It is impossible in general case.
void caller() { A a = callee(); }