I am looking into return value optimization in the case of tuple/ties and the behavior I observe is not as I expected. In the example below I would expect move semantics to kick in, which it does, but there is one copy operation which remains. The output from the below in optimized is:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Copy constructor invoked
Move Assignment operator invoked
100
The invocation of the copy constructor in making the tuple inside the function seems unnecessary. Is there any way to remove this? I am using the MSVC 2012 compiler.
#include <iostream>
#include <tuple>
class A
{
public:
int value;
A() : value(-1)
{
std::cout << "Default constructor invoked" << std::endl;
}
explicit A(const int v) : value(v)
{
std::cout << "Parameter constructor invoked" << std::endl;
}
A(const A& rhs)
{
value = rhs.value;
std::cout << "Copy constructor invoked" << std::endl;
}
A(const A&& rhs)
{
value = rhs.value;
std::cout << "Move constructor invoked" << std::endl;
}
A& operator=(const A& rhs)
{
value = rhs.value;
std::cout << "Assignment operator invoked" << std::endl;
return *this;
}
A& operator=(const A&& rhs)
{
value = rhs.value;
std::cout << "Move Assignment operator invoked" << std::endl;
return *this;
}
};
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(tmp, 99);
}
int main(int argc, char* argv[])
{
std::cout << "Test duo output, non_reference tuple" << std::endl;
A t3;
int v1;
std::tie(t3, v1) = return_two_non_reference_tuple();
std::cout << t3.value << std::endl << std::endl;
system("pause");
return 0;
}
The move constructor will not be called automatically because you are calling
std::make_tuple(tmp, 99);
In this case, tmp
is an lvalue. You can use std::move
to cast it to an rvalue reference:
return std::make_tuple(std::move(tmp), 99);
This will instruct the compiler to use the move constructor.
The copy occurs here:
std::make_tuple(tmp, 99);
Although you can see that tmp
might be able to be directly constructed in the tuple, the copy from tmp
to the tuple will not be elided. What you really want is a way to pass in arguments for std::tuple
to use to construct its internal A
and int
objects. There isn't such a thing for std::tuple
, but there is a way to achieve the same effect.
Since you only have two types, you could use std::pair
. This has a std::piecewise_construct
constructor, which takes two std::tuples
containing the arguments to pass to the constructors of the internal objects.
std::pair<A, int> return_two_non_reference_tuple()
{
return {std::piecewise_construct,
std::make_tuple(100), std::make_tuple(99)};
}
The cool thing about this solution is that you can still use std::tie
at the call site, because std::tuple
has an assignment operator from std::pair
.
std::tie(t3, v1) = return_two_non_reference_tuple();
As you can see from the output, your copy is gone. It isn't replaced by a move like in the other answers, it's totally removed:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Move Assignment operator invoked
100
Live Demo
The copy takes place in return_two_non_reference_tuple()
.
One way to remove it is to move tmp
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
}
or another way
std::tuple<A, int> return_two_non_reference_tuple()
{
return std::make_tuple(A(100), 99);
}
You are not moving tmp
:
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With