Why can we use `std::move` on a `const` object?
In C++11, we can write this code:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
when I call std::move
, it means I want to move the object, i.e. I will change the object. To move a const
object is unreasonable, so why does std::move
not restrict this behaviour? It will be a trap in the future, right?
Here trap means as Brandon mentioned in the comment:
" I think he means it "traps" him sneaky sneaky because if he doesn't realize, he ends up with a copy which is not what he intended."
In the book 'Effective Modern C++' by Scott Meyers, he gives an example:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
If std::move
was forbidden from operating on a const
object, we could easily find out the bug, right?
Solution 1:
There's a trick here you're overlooking, namely that std::move(cat)
doesn't actually move anything. It merely tells the compiler to try to move. However, since your class has no constructor that accepts a const CAT&&
, it will instead use the implicit const CAT&
copy constructor, and safely copy. No danger, no trap. If the copy constructor is disabled for any reason, you'll get a compiler error.
struct CAT
{
CAT(){}
CAT(const CAT&) {std::cout << "COPY";}
CAT(CAT&&) {std::cout << "MOVE";}
};
int main() {
const CAT cat;
CAT cat2 = std::move(cat);
}
prints COPY
, not MOVE
.
http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Note that the bug in the code you mention is a performance issue, not a stability issue, so such a bug won't cause a crash, ever. It will just use a slower copy. Additionally, such a bug also occurs for non-const objects that don't have move constructors, so merely adding a const
overload won't catch all of them. We could check for the ability to move construct or move assign from the parameter type, but that would interfere with generic template code that is supposed to fall back on the copy constructor.
And heck, maybe someone wants to be able to construct from const CAT&&
, who am I to say he can't?
Solution 2:
struct strange {
mutable size_t count = 0;
strange( strange const&& o ):count(o.count) { o.count = 0; }
};
const strange s;
strange s2 = std::move(s);
here we see a use of std::move
on a T const
. It returns a T const&&
. We have a move constructor for strange
that takes exactly this type.
And it is called.
Now, it is true that this strange type is more rare than the bugs your proposal would fix.
But, on the other hand, the existing std::move
works better in generic code, where you don't know if the type you are working with is a T
or a T const
.
Solution 3:
One reason the rest of the answers have overlooked so far is the ability for generic code to be resilient in the face of move. For example lets say that I wanted to write a generic function which moved all of the elements out of one kind of container to create another kind of container with the same values:
template <class C1, class C2>
C1
move_each(C2&& c2)
{
return C1(std::make_move_iterator(c2.begin()),
std::make_move_iterator(c2.end()));
}
Cool, now I can relatively efficiently create a vector<string>
from a deque<string>
and each individual string
will be moved in the process.
But what if I want to move from a map
?
int
main()
{
std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
for (auto const& p : v)
std::cout << "{" << p.first << ", " << p.second << "} ";
std::cout << '\n';
}
If std::move
insisted on a non-const
argument, the above instantiation of move_each
would not compile because it is trying to move a const int
(the key_type
of the map
). But this code doesn't care if it can't move the key_type
. It wants to move the mapped_type
(std::string
) for performance reasons.
It is for this example, and countless other examples like it in generic coding that std::move
is a request to move, not a demand to move.