I'm trying to implement a container that provides insert methods with both copy and move semantics.
The implementation is something like this:
template <typename T> class Buffer {
// lots of stuff ommitted here
...
// copy semantics
Buffer<T>::Iterator push(const T& item) {
Buffer::Iterator head = push();
*head = item;
return head;
}
// move semantics
Buffer<T>::Iterator push(T&& item) {
Buffer::Iterator head = push();
*head = std::move(item);
return head;
}
}
This works fine if type T (the type to be pushed into the buffer) implements a move assignment operator. However, I get a compiler error if I try to push instances of something like this:
struct Foo {
Foo(int a) : m_bar(a) {}
int m_bar;
Foo& operator=(Foo& other) {
this.m_bar = other.m_bar;
}
}
If I try to compile buffer.push(Foo(42));
I get a compiler error for the push(T&& item)
-method on the line that reads *head = std::move(item);
. The error is that there is no viable overload for operator=
that accepts an rvalue - which is correct, there isn't one. There is only an assignment operator that accepts lvalues.
But since I can't make sure that every object ever to be stored in my container will have a properly implemented move assignment operator I need to make sure that this case is handled correctly. What's more, std::vector handles this without a problem. When an object implements move assignment push_back
will move it, if not it will copy it. Regardless if it is an rvalue or not. In fact if I put my problematic Foo
rvalue that caused the error earlier into a std::vector it works like it should.
So what am I missing? How can my container implement move semantics and still support rvalue references for objects that don't implement move assignment?
What you are doing/assuming wrong is the incorrect signature of a copy-assignment operator:
Foo& operator=(Foo& other);
which takes a non-const lvalue reference to the other
instance. This prevents a move from falling back into a regular copy if there is no user-provided assignment operator taking an rvalue reference (that is, an rvalue can be bound by a const lvalue reference), so it should be:
Foo& operator=(const Foo& other);
// ~~~~^
Then why it works with
std::vector<Foo>
then ?
The buffer.push_back(Foo(42));
statement utilizes a copy constructor, not an assignment operator. This works, since Foo
has an implicitly generated copy-constructor of the following signature:
Foo(const Foo&);
which works for both lvalues and rvalues (DEMO).
What you are trying to do:
*head = std::move(item);
is to use an assignment operator. Since you have declared one on your own, a compiler can't generate implicitly the one taking a const lvalue reference, and it can't also use the user-declared one taking a non-const lvalue reference, which results in the error you see.
Consider using allocators or the placement-new operator in your push
operations, using the item
parameter as the argument for the constructor of Foo
, instead of using copy-assignment and move-assignment operators.