Reputation: 21560
I am trying to understand how perfect forwarding works but I cannot understand why the copy constructor is called in the code below
#include <utility>
#include <iostream>
using std::cout;
using std::endl;
class Something {
public:
Something() = default;
Something(__attribute__((unused)) const Something& other) {
cout << "Copy constructor called" << endl;
}
Something(__attribute__((unused)) Something&& other) {
cout << "Move constructor called" << endl;
}
void print() {
cout << "Something::print() called" << endl;
}
};
void function_1(Something&& one) {
cout << "version two called" << endl;
Something inner{one};
inner.print();
}
void function_1(const Something& one) {
Something inner(one);
inner.print();
}
template <typename... T>
void test_function(T&&... ts) {
function_1(std::forward<T>(ts)...);
}
int main() {
const Something some1 {Something()};
test_function(some1);
test_function(Something());
return 0;
}
This produces the following output
Copy constructor called
Something::print() called
version two called
Copy constructor called
Something::print() called
Changing the code to include std::move
in the rvalue reference works but I did not expect to need it. When a reference is an rvalue reference the correct constructor should be called automatically right? The correct reference is resolved but the wrong constructor is being called. Any help would be greatly appreciated!
Upvotes: 0
Views: 101
Reputation: 16224
This code will call the copy ctor, not the move ctor.
void function_1(Something&& one) {
cout << "version two called" << endl;
Something inner{one};
inner.print();
}
This code calls the move ctor.
void function_1(Something&& one) {
cout << "version two called" << endl;
Something inner{std::move(one)};
inner.print();
}
The expression one
is technically an l-value. It refers to an rvalue-reference. But to actually get the rvalue-reference you have to use std::move
. Generally anything that has a name is an l-value. Unnamed temporaries, like your Something()
expression in main()
:
test_function(Something());
can be rvalue's and can invoke a move without using std::move
.
Upvotes: 1
Reputation: 275896
An rvalue reference binds to rvalues. It is not itself an rvalue, for it has a name.
But anything with a name at point of use is an lvalue by default, even rvalue references. Your code could use Something&& one
three times, and if the first use implicitly move
s you would be screwed.
Instead, it is an lvalue at point of use (by default), and it binds to an rvalue.
When you want to signal you no longer require its state to persist, std::move
it.
Perfect forwarding can be used to write both of your function_1
s by putting a std::forward<Blah>(blah)
at the point where you'd want to move from blah if it was an rvalue reference.
Now the above is full of lies, for there are xvalues prvalues lvalues etc -- the standard is more complex. The use of a variable in return statements can turn a named value into an rvalue, for example. But the basic rule of thumb is worth knowing: it has a name, it is an lvalue (except if explicitly casted, or expiring).
Upvotes: 4