Reputation: 10519
I don't understand in the following example why the paramater in the assignement operator use the copy constructor and not the move constructor to be builded
struct Foo
{
int data;
Foo()
{
static int cpt = 1;
data = cpt++;
std::cout << "Foo\n";
}
Foo(const Foo& foo)
{
std::cout << "const Foo&\n";
data = foo.data;
}
Foo(Foo&& foo)
{
std::cout << "Foo&&\n";
data = foo.data;
foo.data = 0;
}
Foo& operator= (Foo foo) //<--- call the copy ctor and not the move ctor as I expected
{
data = foo.data;
std::cout << "operator=\n";
return *this;
}
Foo& operator+ (const Foo& foo)
{
data += foo.data;
return *this;
}
};
int main()
{
Foo f;
Foo f1;
Foo f3;
f3 = f + f1;
std::cout << f3.data;
std::cin.ignore();
return 1;
}
output:
Foo
Foo
Foo
const Foo&
operator=
3
compiler : MSVCS2012 CTP
EDIT:
"But if you say a = x + y, the move constructor will initialize that (because the expression x + y is an rvalue)..."
But in fact x + y is a rvalue only if operator +
is implemented in a "special" way so ?
Upvotes: 0
Views: 832
Reputation: 20553
As it has been pointed out by others, the problem is operator +()
. Specifically, it returns a Foo&
, that is, an lvalue reference. Since an lvalue reference is an lvalue (this is not a tautology) it cannot be bound to an rvalue reference. Therefore, Foo(Foo&&)
cannot be called.
I'm not going to say how the operator +()
and operator +=()
should be implemented (see other answers and the comments therein for more on this topic). I'm going to focus on rvalue x lvalue. As said in Scott Meyers' Universal References in C++11
A precise definition for these terms is difficult to develop (the C++11 standard generally specifies whether an expression is an lvalue or an rvalue on a case-by-case basis) ...
However, the following functions can be used to check if an object is an lvalue or rvalue:
#include <type_traits>
template <typename T>
bool is_lvalue(T&&) {
return std::is_reference<T>::value;
}
template <typename T>
bool is_rvalue(T&&) {
return !std::is_reference<T>::value;
}
For example, the following code
struct bar {
bar& operator+(const bar&) { return *this; }
bar&& operator-(const bar&) { return std::move(*this); }
};
int main() {
bar b1;
bar b2;
std:: cout << std::boolalpha;
std:: cout << is_rvalue( b1 + b2) << std::endl;
std:: cout << is_rvalue( b1 - b2) << std::endl;
}
outputs
false
true
Note: bar
, inspired by the original's code, uses operator +()
and operator -()
but for this matter there's nothing special about being operators.
Functions is_lvalue()
and is_rvalue()
can be improved. For instance they can be constexrp
but I'm keeping it simple here because some compilers (notably, VS2012) don't implement constexpr
yet.
The explanation on how these function works, is based on how T
is deduced. I quote the aforementioned Scott Meyers' article:
During type deduction for a template parameter [ ... ], lvalues and rvalues of the same type are deduced to have slightly different types. In particular, lvalues of type T are deduced to be of type T& (i.e., lvalue reference to T), while rvalues of type T are deduced to be simply of type T.
Upvotes: 1
Reputation: 56921
You need to provide two overloads for operator=
because your code does not move data
:
Foo& operator= (const Foo& foo)
{
data = foo.data;
return *this;
}
// only needed if the implementation can benefit from a movable object
Foo& operator= (Foo&& foo)
{
data = std::move(foo.data); // without std::move, as above, the copy-ctor is called
return *this;
}
That said, what is wrong with this signature:
Foo& operator= (Foo foo);
? The problem is, that you always force an object to be passed, which can lead to inefficiencies. Consider:
Foo f, f2;
f = f2;
(of course, in the example the optimizer will most likely remove all overhead).
Generally, operator=
does not need a copy, so it shouldn't request one. See also Andy Prowl's nice take on how to take parameters "correctly".
Upvotes: 0
Reputation: 477710
Your assumption is incorrect. The assignment operator uses whichever constructor is available:
Foo a;
Foo b;
Foo x;
x = a; // copy constructor
x = std::move(b); // move constructor
x = Foo(); // move constructor
Your misunderstanding comes from your awkward operator+
, which doesn't return an rvalue as you might be believing.
Update: A proper "plus" operator pair would look like this:
Foo& operator+= (const Foo& foo)
{
data += foo.data;
return *this;
}
Foo operator+ (const Foo& rhs)
{
return Foo(*this) += rhs;
}
(In general, you could also make those signatures Foo rhs
and use std::move(rhs)
if appropriate, though it makes no difference for this simple class.)
Upvotes: 3