Copy elision with tuples - c++

I always had the wrong impression that if I create temporaries in a function that returns a tuple, and use std::forward_as_tuple in the return statement, then there is no copy, just like automatic copy elision for non-tuple return types.
How then can we avoid a copy? For example, in the following code, func1 “copies” the values 1 and 2 into temp as it is being constructed, and there is no additional copy upon returning. func2 on the other hand does the same construction, but additionally copies when a tuple is created in the return statement.
I tried to be witty about it, and declare the temporary to be returned a tuple and construct a My_struct in place, like this: std::tuple<My_struct> temp {{1,2}}. But the copy constructor is still called. Adding a move constructor won't help as far as I can see since the data members are simple types.
#include <iostream>
#include <tuple>
struct My_struct {
int x, y;
My_struct(int x, int y) :
x(x), y(y)
{
std::cout << "Constructor called\n";
}
My_struct(const My_struct &a) :
x(a.x), y(a.y)
{
std::cout << "Copy constructor called!\n";
}
};
My_struct func1()
{
My_struct temp {1,2};
return temp;
}
std::tuple<My_struct> func2()
{
My_struct temp {1,2};
return std::forward_as_tuple(temp);
}
int main()
{
std::cout << "# Calling func1\n";
auto result1 = func1();
std::cout << "# Calling func2\n";
auto result2 = func2();
}
result:
# Calling func1
Constructor called
# Calling func2
Constructor called
Copy constructor called!

Related

When are local variables in a function scope destroyed? [duplicate]

This question already has answers here:
Does returning a local variable return a copy and destroy the original(nrvo)?
(1 answer)
What is meaning of destroying local variable in function
(7 answers)
How is destroying local variables when a block is exited normally called in C++?
(6 answers)
Closed 7 months ago.
After a function is called, when the local (non-static) objects will be destroyed has been vague to me, especially after C++17 where prvalue is redefined. So I decide to ask this question.
(In C++14 and earlier)
Assume there is no optimization, consider the following toy code:
class Y
{
public:
~Y() { cout << "quitting f()\n"; }
};
class X
{
public:
X& operator=(const X& x)
{
cout << "assignment\n";
return *this;
}
};
X f()
{
X x;
Y y;
return x;
}
int main()
{
X x;
x = f();
}
The outputs are as follows:
quitting f()
assignment
Question 1:
In my current understanding, the local variables of f() are destroyed immediately after the returned temporary is created. May I ask if this is true?
(In C++17 and newer)
Consider the following toy code:
class Z { };
class Ya
{
public:
~Ya() { cout << "quitting f1()\n"; }
};
class Yb
{
public:
~Yb() { cout << "quitting f2()\n"; }
};
class X
{
public:
X() {}
X(Z z) { cout << "constructing X\n"; }
X& operator=(const X& x)
{
cout << "assignment\n";
return *this;
}
};
X f1()
{
Z z;
Ya y;
return X(z);
}
X f2()
{
Yb y;
return f1();
}
int main()
{
X x;
x = f2();
}
The outputs are as follows:
constructing X
quitting f1()
quitting f2()
assignment
In my current understanding, X(z) is a prvalue which is used to initialize the prvalue represented by f1(), which is then used to initialize the prvalue represented by f2(). The prvalue represented by f2() is then materialized into a temporary which is then used in the copy assignment.
If my understanding to question 1 is correct, I would guess that the local variables z and y are destroyed immediately after the initialization of the prvalue represented by f1(). Assume this is true, there is a problem: before the prvalue represented by f2() is materialized, there is NO object constructed from X(z) exists, so how could the materialized temporary be created from X(z) at the point when z is already destroyed?
Question 2:
As a result, my guess is that the local variables of f1() are destroyed after the prvalue represented by f2() is materialized (or if the prvalue is used to initialize a variable, had we written X x = f2(); instead). May I ask if this is true?
Your question can be answered with a slightly more detailed example. Here is code that traces when every object is constructed and destroyed:
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <type_traits>
// Compile-time string suitable as a non-type template argument.
template<size_t N>
struct fixed_string {
char value[N];
consteval fixed_string(const char (&str)[N]) { std::copy_n(str, N, value); }
};
template<fixed_string Name>
class tracer {
std::ostream &out() const { return std::cout << Name.value << " "; }
public:
tracer() { out() << "default constructed\n"; }
tracer(const tracer &) { out() << "copy constructed\n"; }
tracer(tracer &&) { out() << "move constructed\n"; }
template<typename T> tracer(T t) {
out() << "template constructed [" << typeid(T).name() << "]\n";
}
~tracer() { out() << "destroyed\n"; }
tracer &operator=(const tracer &) {
out() << "copy assigned\n"; return *this;
}
tracer &operator=(tracer &&) { out() << "move assigned\n"; return *this; }
};
tracer<"X">
f1()
{
tracer<"Z"> z;
tracer<"==== f1 temp"> y;
return {z};
}
tracer<"X">
f2()
{
tracer<"==== f2 temp"> y;
return f1();
}
int
main()
{
tracer<"X"> x = f2();
}
The output (with C++20) is as follows:
==== f2 temp default constructed
Z default constructed
==== f1 temp default constructed
Z copy constructed
X template constructed [6tracerIXtl12fixed_stringILm2EEtlA2_cLc90EEEEE]
Z destroyed
==== f1 temp destroyed
Z destroyed
==== f2 temp destroyed
X destroyed
So you can see that only one X is ever created, but that X is created before the Z it is constructed from is destroyed. How does that happen? Well, in the implementation you can think of the location in which to construct the X being passed as a kind of implicit argument to the functions that return a prvalue, so that the X is constructed inside f2, but it is located in main's stack.
Your example is slightly more complicated, but the same phenomenon is happening:
int
main()
{
tracer<"X"> x;
x = f2();
}
Produces the following output:
X default constructed
==== f2 temp default constructed
Z default constructed
==== f1 temp default constructed
Z copy constructed
X template constructed [6tracerIXtl12fixed_stringILm2EEtlA2_cLc90EEEEE]
Z destroyed
==== f1 temp destroyed
Z destroyed
==== f2 temp destroyed
X move assigned
X destroyed
X destroyed
So now obviously we have two X's, the one called x, and the temporary one that is materialized to pass into x's move assignment operator. However, the temporary X is created on main's stack, and its lifetime is the full expression, so if there were more code in main, the temporary X would be destroyed at the semicolon after the assignment, rather than at main's closing brace.

What is the best way to return multiple large objects in C++?

I want to return a tuple containing types like std::vector or std::unordered_map etc. where the objects may be large enough that I care about not copying. I wasn't sure how copy elision / return value optimization will work when the returned objects are wrapped in a tuple. To this end I wrote some test code below and am confused by parts of its output:
#include <tuple>
#include <iostream>
struct A {
A() {}
A(const A& a) {
std::cout << "copy constructor\n";
}
A(A&& a) noexcept {
std::cout << "move constructor\n";
}
~A() {
std::cout << "destructor\n";
}
};
struct B {
};
std::tuple<A, B> foo() {
A a;
B b;
return { a, b };
}
std::tuple<A, B> bar() {
A a;
B b;
return { std::move(a), std::move(b) };
}
std::tuple<A, B> quux() {
A a;
B b;
return std::move(std::tuple<A, B>{ std::move(a), std::move(b) });
}
std::tuple<A, B> mumble() {
A a;
B b;
return std::move(std::tuple<A, B>{ a, b });
}
int main()
{
std::cout << "calling foo...\n\n";
auto [a1, b1] = foo();
std::cout << "\n";
std::cout << "calling bar...\n\n";
auto [a2, b2] = bar();
std::cout << "\n";
std::cout << "calling quux...\n\n";
auto [a3, b3] = quux();
std::cout << "\n";
std::cout << "calling mumble...\n\n";
auto [a4, b4] = mumble();
std::cout << "\n";
std::cout << "cleaning up main()\n";
return 0;
}
when I run the above (on VS2019) I get the following output:
calling foo...
copy constructor
destructor
calling bar...
move constructor
destructor
calling quux...
move constructor
move constructor
destructor
destructor
calling mumble...
copy constructor
move constructor
destructor
destructor
cleaning up main()
destructor
destructor
destructor
destructor
So from the above is looks like bar() is best which is return { std::move(a), std::move(b) }. My main question is why foo() ends up copying? RVO should elide the tuple from being copied but shouldn't the compiler be smart enough to not copy the A struct? The tuple constructor could be a move constructor there since it is firing in an expression that is being returned from a function i.e. because struct a is about to not exist.
I also don't really understand what is going on with quux(). I didnt think that additional std::move() call was necessary but I don't understand why it ends up causing an additional move to actually occur i.e. I'd expect it to have the same output as bar().
My main question is why foo() ends up copying? RVO should elide the
tuple from being copied but shouldn't the compiler be smart enough to
not copy the A struct? The tuple constructor could be a move
constructor
No, move constructor could only construct it from another tuple<> object. {a,b} is constructing from the component types, so the A and B objects are copied.
what it going on with quux(). I didnt think that additional
std::move() call was necessary but I don't understand why it ends up
causing an additional move to actually occur i.e. I'd expect it to
have the same output as bar().
The 2nd move happens when you are moving the tuple. Moving it prevents the copy elision that occurs in bar(). It is well-know that std::move() around the entire return expression is harmful.

Allowing both moving and copying a returned class member

I'm trying to write a class that contains a function returning one of the class members, and I want to allow the caller to either move or copy the returned value. I wrote some dummy structs to test this; and after trying different variations, this seems to give me what I want.
#include <iostream>
using namespace std;
struct S {
int x;
S() : x(10) { cout << "ctor called\n"; }
S(const S& s) : x(s.x) { cout << "copy ctor called\n"; }
S(S&& s) : x(s.x) { cout << "move ctor called\n"; }
// I'm implementing move and copy the same way since x is an int.
// I just want to know which one gets called.
};
struct T {
S s;
T() : s() {}
S&& Test() && {
return move(s);
}
const S& Test() & {
return s;
}
};
int main() {
T t;
auto v = move(t).Test();
cout << v.x << "\n";
T t2;
auto w = t2.Test();
cout << w.x << "\n";
return 0;
}
The code prints out (with clang++-5.0 c++14):
ctor called
move ctor called
10
ctor called
copy ctor called
10
Is this an acceptable way to implement what I want? I have a few questions:
In the first Test function, I tried both S&& and S for the return type and it doesn't change the output. Does && mean anything for the (non-template) returned type?
Is it guaranteed that auto v = move(t).Test() would only invalidate the "moved" member? If struct T had other member variables, can I assume this call wouldn't invalidate them?
In the first Test function, I tried both S&& and S for the return type and it doesn't change the output. Does && mean anything for the (non-template) returned type?
There are little differences:
S&& is a (r-value) reference, so object is not yet moved.
returning S would move-construct S, so member is moved once the method is called.
For move(t).Test();, return ingS&& does nothing whereas returning S would move the member.
Is it guaranteed that auto v = move(t).Test() would only invalidate the "moved" member? If struct T had other member variables, can I assume this call wouldn't invalidate them?
Yes, only T::s is moved. std::move is just a cast to rvalue.
Yes it is acceptable way to implement this.
It does the same thing because returned value is temporary object, thus rvalue.
Depends on what you mean by invalidating

Lambda-specific variable

Is there any way to create variable that will be unique for some lambda function and will last between launches of lambda?
More careful description: I want lambda with variable initialized to some value, and that variable should last between launches:
std::function<void(void)> a=[]()
{
/*here we declare variable X and initialize it to 0*/;
std::cout<<X++;
};
a();a();
So this should print out 01
But also I need to be sure that "X" is unique for "a", so after previous part this
std::function<void(void)> b=a;
b();b();
should print out 01.
I tried using static variables, but they are shared between copies(so these two parts print out 0123).
So, is there any way to do it?
I don't think mutable lambdas are sufficient. The mutable capture will get copied, when you copy the function pointer, also copying the counter. My read of the question, is that each copy of the lambda should start with the initial mutable capture.
You need to capture a custom class, with a copy constructor, to do this:
#include <functional>
#include <iostream>
class my_class {
public:
int n=0;
my_class()
{
}
my_class(const my_class &b)
{
}
};
int main()
{
std::function<void(void)> a=
[my_class_instance=my_class()]()
mutable
{
std::cout << my_class_instance.n++;
};
a();
a();
auto b=a;
b();
b();
}
The result from this is:
0101
Without a helper class, the equivalent code using only mutable lambdas will produce a
0123
My read of the question, is that the former behavior is desired.
You want it to reset on copies. Make data that does this:
template<class T>
struct no_copy {
T init;
T current;
operator T&(){ return current; }
operator T const&()const{ return current; }
no_copy( T&& i ):init(i), current(init) {}
no_copy( no_copy const&o ):init(o.init), current(init) {}
no_copy( no_copy &&o ):init(std::move(o.init)), current(init) {}
};
template<class T>
no_copy<std::decay_t<T>> make_no_copy(T&& t){
return {std::forward<T>(t)};
}
Then, in C++14, easy:
std::function<void(void)> a=[X=make_no_copy(0)]()mutable
{
std::cout<<X++;
};
a();a();
prints out 01.
In C++11:
auto X=make_no_copy(0);
std::function<void(void)> a=[X]()mutable
{
std::cout<<X++;
};
a();a();
it also works, but is a bit more ugly.
Other than a copy of X existing outside of the lambda, the C++11 version is the same as the C++14 version in behavior.
live example
Is using the copy constructor for "resetting" the only option? Shouldn't you be instead writing a factory function that emits fresh lambdas from the same initial environment?
Expecting that stuff A is different from stuff B after a copy is abuse of semantics.
auto make_counter() -> std::function<int()> {
return [x=0]() mutable { return x++; };
}
auto a = make_counter();
std::cout << a() << " " << a() << "\n";
auto b = make_counter();
std::cout << b() << " " << b() << "\n";

Copy constructor not called when initializing an object with return value of a function

Consider the following code:
#include <iostream>
using namespace std;
class A
{
public:
int a;
A(): a(5)
{
cout << "Constructor\n";
}
A(const A &b)
{
a = b.a;
cout << "Copy Constructor\n";
}
A fun(A a)
{
return a;
}
};
int main()
{
A a, c;
A b = a.fun(c);
return 0;
}
The output of the above code with g++ file.cpp is:
Constructor
Constructor
Copy Constructor
Copy Constructor
The output of the above code with g++ -fno-elide-constructors file.cpp is:
Constructor
Constructor
Copy Constructor
Copy Constructor
Copy Constructor
I know Return Value Optimization. My question is which call to copy constructor is elided(temporary object during returning or returned object being copied to b)?
If the elided copy constructor is the one used for creating b, then how is b created at all (because there is no constructor call in this case also)?
If I replace the line A b = a.fun(c); with a.fun(c) and compile using the first method or even the second method, then also the copy constructor is being called 2 times . So, if in the case explained in the previous paragraph, the temporary object's copy constructor is elided, then why isn't it elided in this case?
#include <iostream>
using namespace std;
class A
{
public:
int a;
A(): a(5)
{
cout << "Constructing: " << (void *)this << std::endl;
}
A(const A &b)
{
a = b.a;
cout << "Copy Constructor: " << (void *)this << " from " << (void *)&b << std::endl;
}
A fun(A a)
{
return a;
}
};
int main()
{
A a, c;
A b = a.fun(c);
std::cout << "a:" << (void *)&a << std::endl <<
"b:" << (void *)&b << std::endl <<
"c:" << (void *)&c << std::endl;
return 0;
}
Yields:
Constructing: 0x7fffbb377220
Constructing: 0x7fffbb377210
Copy Constructor: 0x7fffbb377230 from 0x7fffbb377210
Copy Constructor: 0x7fffbb377200 from 0x7fffbb377230
a:0x7fffbb377220
b:0x7fffbb377200
c:0x7fffbb377210
So it constructs a, constructs c, copies c to an intermediate (argument a of the function), and then copies the intermediate directly into b, skipping the typical copying of a to a return intermediate. This is even better demonstrated if you pass by value (change to A fun(const A& a):
Constructing: 0x7fff8e9642b0
Constructing: 0x7fff8e9642a0
Copy Constructor: 0x7fff8e964290 from 0x7fff8e9642a0
a:0x7fff8e9642b0
b:0x7fff8e964290
c:0x7fff8e9642a0
a is constructed, c is constructed, c is copied directly to b, despite b not being passed to fun!
The copy that is elided is the copy of the temporary return value into b. Without elision the return value is initialized from a and copied to b. Instead, the temporary that would otherwise hold the return value is constructed into b and initialized with a. [class.copy]/31:
when a temporary class object that has not been bound to a reference
(12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move
You can observe this if you add an additional output in fun:
A fun(A a)
{
cout << "fun!" << endl;
return a;
}
Then with the elision you'll get
[…]
fun!
Copy Constructor
And without:
[…]
fun!
Copy Constructor
Copy Constructor