175

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?

camino
  • 10,085
  • 20
  • 64
  • 115
  • 4
    But try to move it. Try to change its state. `std::move` by itself doesn't do anything to the object. One could argue `std::move` is poorly named. – juanchopanza Feb 18 '15 at 22:25
  • 4
    It doesn't actually *move* anything. All it does is cast to an rvalue reference. try `CAT cat2 = std::move(cat);`, assuming `CAT` supports regular move-assignment. – WhozCraig Feb 18 '15 at 22:25
  • 22
    `std::move` is just a cast, it does not actually move anything – Red Alert Feb 18 '15 at 22:25
  • 2
    @WhozCraig: Careful, as the code you posted compiles and executes without warning, making it sort of misleading. – Mooing Duck Feb 18 '15 at 22:29
  • 1
    @MooingDuck Never said it wouldn't compile. it works only because the default copy-ctor is enabled. Squelch that and the wheels fall off. – WhozCraig Feb 18 '15 at 22:30
  • 1
    If `std::move` was forbidden from operating on a `const` object, it would still be allowed on types that lack a move constructor, which would still silently copy, exactly what you want to prevent. –  Feb 18 '15 at 22:53
  • 2
    @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 Feb 18 '15 at 23:30
  • 1
    I'd say there's nothing wrong with `move` in Scott's snippet. On the other hand taking an argument by `const value` is odd and you usually don't want to do that. That's really in a section about `move`? – AliciaBytes Feb 18 '15 at 23:45
  • @ChrisDrew But it doesn't even fix the bug the OP wants to fix (insofar as it can be called a bug). The OP wants to fix copy constructors being called even when the caller spells out `std::move`. That would still be the case with the OP's change. –  Feb 19 '15 at 06:38
  • 1
    Poor question : the problem is that you're trying to move a cat, which can't succeed by definition. – Quentin Feb 20 '15 at 02:02
  • Compiler actually have all needed information to tell if move semantic will be applied. At least compiler should give a choice of turning on warnings on such ridiculous errors. I just found dummy error in my code where I took `const Value &value = stack.top()` instead of `Value &value = stack.top()` before doing `std::move(value)`. – ony Oct 09 '15 at 07:37
  • Warning proposal for gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67906 – ony Oct 09 '15 at 07:57
  • @AliciaBytes If you stress `const`-correctness, you will make anything that doesn't change `const` to document its nature. Here, `value(std::move(text))` is an attempt at changing `text`, so it makes no sense. However, there's nothing wrong with a `const` that came by value. This example is in a chapter about `std::move`, because a person new to the feature needs to know that `std::move` will basically do nothing if you pass it a `const`. – user904963 Feb 26 '22 at 22:04

6 Answers6

157

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?

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Upticked. Worth noting the implicit deletion of the copy-ctor upon user-defined definition of the regular move-constructor or assignment operator will also demonstrate this via broken compilation. Nice answer. – WhozCraig Feb 18 '15 at 22:34
  • Also worth mentioning that a copy-constructor which needs a non-`const` lvalue also won't help. [class.copy] §8: "Otherwise, the implicitly-declared copy constructor will have the form `X::X(X&)`" – Deduplicator Feb 18 '15 at 23:40
  • 17
    I don't think he meant "trap" in computer/assembly terms. I think he means it "traps" him sneaky sneaky because if he doesn't realise, he ends up with a copy which is not what he intended. I guess.. – Brandon Feb 19 '15 at 01:28
  • 1
    Let's write a `const CAT&&` constructor on a `CAT` object that has all `mutable` members :) – bobobobo Oct 09 '21 at 01:28
  • `std::move` isn't an attempt to move something. All it does is change a type `T` to `T&&` (or `const T` to `const T&&`). That could be done anywhere, including places where "moving" has no meaning such as a function like `int f(int const &&i) { return 2; }`. – user904963 Feb 26 '22 at 22:19
  • if `std::move` may not actually *move* anything, it should be called `std::move_or_copy` then. – qkhhly Nov 17 '22 at 22:32
  • @qkhhly `std::enable_move_from`? – Mooing Duck Dec 02 '22 at 08:07
  • `std::permission_granted_to_suck_out_the_objects_innards_and_leave_behind_an_empty_husk` – Eljay Dec 30 '22 at 12:43
74
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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 8
    +1 for being the first answer that actually attempts to explain why you would _want_ to call `std::move` on a `const` object. – Chris Drew Feb 18 '15 at 22:46
  • 2
    +1 for showing a function taking `const T&&`. This expresses an "API protocol" of the kind "I will take an rvalue-ref but I promise I won't modify it". I guess, apart from when using mutable, it's uncommon. Maybe another use case is to be able to use `forward_as_tuple` on almost anything and later use it. – F Pereira Mar 19 '20 at 00:56
25

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
2

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

Bogeegee
  • 21
  • 1
2

I'm surprised nobody mentioned the backward compatibility aspect of this. I believe, std::move was purposely designed to do this in C++11. Imagine you're working with a legacy codebase, that heavily relies on C++98 libraries, so without the fallback on copy assignment, moving would break things.

mlo
  • 21
  • 1
2

Fortunately you can use clang-tidy's check to find such issues: https://clang.llvm.org/extra/clang-tidy/checks/performance/move-const-arg.html

Zinovy Nis
  • 455
  • 6
  • 9