Implementing insert () in a container with move semantics when rvalue does not implement move
I am trying to implement a container that provides paste methods with copy and move semantics.
The implementation looks 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 great if the type T (the type to be inserted into the buffer) implements the move assignment operator. However, I get a compiler error if I try to push instances 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 method push(T&& item)
on the line that it reads *head = std::move(item);
. The error is that there is no viable overload for operator=
that takes an rvalue - that's right, there isn't. There is only an assignment operator that accepts lvalues.
But since I cannot make sure that every object that will ever be stored in my container will have the move assignment operator correctly implemented, I need to make sure that this case is handled correctly. What's more, std :: vector handles this without issue. When an object implements a redirect destination push_back
, it will move it if it doesn't copy it. Whether it is an rvalue or not. In fact, if I put my problematic Foo
rvalue that was previously throwing an error in the std :: vector, it works as it should.
So what am I missing? How can my container implement move semantics and still maintain rvalue references for objects that do not implement move purpose?
source to share
What you do / think is wrong is the incorrect signature of the copy assignment operator:
Foo& operator=(Foo& other);
which takes a non-const lvalue reference to an instance other
. This prevents backtracking back into a regular copy if the assignment operator assigned by the user does not receive a reference to the rvalue (i.e. the rvalue can be bound by a const lvalue reference ), so it should be:
Foo& operator=(const Foo& other);
// ~~~~^
Then why is he working with
std::vector<Foo>
, then?
The operator buffer.push_back(Foo(42));
uses the copy constructor, not the assignment operator. This works because it Foo
has an implicitly instantiated copy of the following signature:
Foo(const Foo&);
which works for both lvalues and rvalues ( DEMO ).
What are you trying to do:
*head = std::move(item);
- use the assignment operator. Since you declared one yourself, the compiler cannot implicitly generate one that accepts an lvalue constant reference, nor can it use a user-declared member that accepts an lvalue constant reference, which results in the error you see.
Consider using allocators or the placement-new operator in your operations push
, using a parameter item
as an argument to the constructor Foo
instead of using the copy assignment and carry operators.
source to share