60 Intermediate The Hundred
std::move Moves Nothing
the move that never happened
#include <iostream>
#include <string>
#include <utility>
int main() {
std::string first = "a message long enough to defeat the small-string optimization";
const std::string second = "a message long enough to defeat the small-string optimization";
std::string a = std::move(first);
std::string b = std::move(second); // both buffers stolen... right?
std::cout << "first after move: \"" << first << "\"\n";
std::cout << "second after move: \"" << second << "\"\n";
}
Run it. What's left in the two strings?
Answer
first comes out empty (typically). second comes out untouched — every character still
there, because it was quietly copied, not moved.
Why¶
std::move never moves anything — it is just a static_cast to rvalue reference with a
persuasive name, and the cast carries const along with it. That makes its argument
eligible for a move; whether one actually happens is decided afterwards, by ordinary
overload resolution between the move and copy constructors. std::move(second) yields a
const std::string&&, which the move constructor string(string&&) cannot bind to — but
the copy constructor string(const string&) can, so the compiler falls back to it.
Silently: -Wall -Wextra says nothing (GCC 11). The non-const first genuinely moves,
and libstdc++ leaves it empty — but the standard only promises a "valid but unspecified"
state, so treat the emptiness as typical, not guaranteed.
The fix¶
A local you intend to move from must not be const — being moved from is a
modification:
The veto reaches inside classes too, but more narrowly: a const data member is itself
copied during move construction (its sibling members still move), and it deletes move
and copy assignment outright — a compile error there, not a silent copy.
Takeaway: std::move is a request, not a command — const vetoes it, and the veto
is a silent copy.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo