Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can we use `std::move` on a `const` object?

Tags:

c++

c++11

People also ask

Can STD move a const?

If you see code that calls std::move() it looks like it should be calling a move constructor, but you could be wrong. When a move constructor on the type being cast to an rvalue does not exist, the compiler will fall back to binding the rvalue as a const-reference. This means the copy-constructor would be used instead.

What is the purpose of std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Should move constructor be const?

A move constructor should normally take a non-const reference. If it were possible to move from a const object it would usually imply that it was as efficient to copy an object as it was to "move" from it. At this point there is normally no benefit to having a move constructor.

What does move () do in C ++?

std::move in C++Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.


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?


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.


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.


I have the same concern as the OP.

std::move does not move an object, neither guarantees the object is movable. Then why is it called move?

I think being not movable can be one of following two scenarios:

1. The moving type is const.

The reason we have const keyword in the language is that we want the compiler to prevent any change to an object defined to be const. Given the example in Scott Meyers' book:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     { … } // doesn't do what it seems to!    
     …
    private:
     std::string value;
    };

What does it literally mean? Move a const string to the value member - at least, that's my understanding before I reading the explanation.

If the language intends to not do move or not guarantee move is applicable when std::move() is called, then it is literally misleading when using word move.

If the language is encouraging people using std::move to have better efficiency, it has to prevent traps like this as early as possible, especially for this type of obvious literal contradiction.

I agree that people should be aware moving a constant is impossible, but this obligation should not imply the compiler can be silent when obvious contradiction happens.

2. The object has no move constructor

Personally, I think this is a separate story from OP's concern, as Chris Drew said

@hvd That seems like a bit of a non-argument to me. Just because OP's suggestion doesn't fix all bugs in the world doesn't necessarily mean it is a bad idea (it probably is, but not for the reason you give). – Chris Drew