I would expect that in C++20 the following code prints nothing between prints of A and B (since I expect guaranteed RVO to kick in). But output is:
A
Bye
B
C
Bye
Bye
So presumably one temporary is being created.
#include <iostream>
#include <tuple>
struct INeedElision{
int i;
~INeedElision(){
std::cout << "Bye\n";
}
};
std::tuple<int, INeedElision> f(){
int i = 47;
return {i, {47}};
}
INeedElision g(){
return {};
}
int main()
{
std::cout << "A\n";
auto x = f();
std::cout << "B\n";
auto y = g();
std::cout << "C\n";
}
What is the reason for this behavior? Is there a workaround to avoid copy (without using pointers)?
https://godbolt.org/z/zasoGd
When constructing std::tuple<int, INeedElision>
from {i, {47}}
, the selected constructor of std::tuple
takes elements by lvalue-reference to const
.
tuple( const Types&... args );
Then when use {i, {47}}
as the initializer, a temporary INeedElision
will be constructed and then passed to the constructor of std::tuple
(and get copied). The temporary object will be destroyed immediately and you'll see "Bye" between "A" and "B".
BTW: The 3rd constructor of std::tuple
won't be used for this case.
template< class... UTypes > tuple( UTypes&&... args );
It's a constructor template, and braced-init-list like {47}
doesn't have type and can't be deduced by template argument deduction.
On the other hand, if INeedElision
has a converting constructor taking int
, and make the initializer as {i, 47}
, the 3rd constructor of std::tuple
will be used and no temporary INeedElision
is constructed; the element will be constructed in-place from the int
47
.
LIVE
you only get copy elision if you return the object itself :
std::vector<int> fn1()
{
return std::vector<int>{}; // guaranteed copy elision
}
std::vector<int> fn2()
{
std::vector<int> vec;
return vec; // a good compiler will manage to elide the copy/move here
}
in your case you are returning tuple so the tuple itself maybe copy elided but not the arguments passed to the constructor of the tuple !
std::tuple<int, INeedElision> f(){
int i = 47;
return {i, {47}}; // construct the tuple in place of the return address but the arguments are copied into the tuple and not even moved ! to move call std::move explicitly
}
the compiler isn't allowed to elide the copy of arguments passed to the tuple constructor because you aren't returning the arguments themselves but rather the tuple containing copy of them . Also note that the table can't hold references to the arguments because these local variables will have been destructed by the time the function returns resulting in a dangling references .
if you want to get a chance for copy elision in c++ 17 and later do something like this :
std::tuple<int, INeedElision> f(){
std::tuple<int, INeedElision> ret;
auto& [i, ne] = ret;
i = 47;
ne = 47;
return ret;
}
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