I've come across some behavior I cannot wrap my head around regarding rvalue return. Let's say we have the following structs:
struct Bar
{
int a;
Bar()
: a(1)
{
std::cout << "Default Constructed" << std::endl;
}
Bar(const Bar& Other)
: a(Other.a)
{
std::cout << "Copy Constructed" << std::endl;
}
Bar(Bar&& Other)
: a(Other.a)
{
std::cout << "Move Constructed" << std::endl;
}
~Bar()
{
std::cout << "Destructed" << std::endl;
}
Bar& operator=(const Bar& Other)
{
a = Other.a;
std::cout << "Copy Assigment" << std::endl;
return *this;
}
Bar& operator=(Bar&& Other) noexcept
{
a = Other.a;
std::cout << "Move Assigment" << std::endl;
return *this;
}
};
struct Foo
{
Bar myBar;
Bar GetBar()
{
return myBar;
}
// Note that we are not returning Bar&&
Bar GetBarRValue()
{
return std::move(myBar);
}
Bar&& GetBarRValueExplicit()
{
return std::move(myBar);
}
};
Being used as followed:
int main()
{
Foo myFoo;
// Output:
// Copy Constructed
Bar CopyConstructed(myFoo.GetBar());
// Output:
// Move Constructed
Bar MoveConstructedExplicit(myFoo.GetBarRValueExplicit());
// Output:
// Move Constructed
//
// I don't get it, GetBarRValue() has has the same return type as GetBar() in the function signature.
// How can the caller know in one case the returned value is safe to move but not in the other?
Bar MoveConstructed(myFoo.GetBarRValue());
}
Now I get why Bar MoveConstructedExplicit(myFoo.GetBarRValueExplicit())
calls the move constructor.
But since the function Foo::GetBarRValue()
does not explicitly returns a Bar&&
I expected its call to give the same behavior as Foo::GetBar()
. I don't understand why/how the move constructor is called in that case. As far as I know, there is no way to know that the implementation of GetBarRValue()
casts myBar
to an rValue reference.
Is my compiler playing optimization tricks on me (testing this in debug build in Visual Studio, apparently return value optimizations cannot be disabled)?
What I find slightly distressing is the fact that the behavior on the caller's side can be influenced by the implementation of GetBarRValue()
. Nothing in the GetBarRValue()
signature tells us it will give undefined behavior if called twice. Seems to me because of this it's bad practice to return std::move(x)
when the function does not explicitly returns a &&.
Can someone explain to me what is happening here? Thanks!