No type is safe to use after a move, unless it's documented to put itself in to a well defined state and says as such
You can't magically make all the member functions on std::vector safe after a move for example unless the moved from vector allocates itself a new (empty) buffer, which kills the performance benefits.
I believe it's actually the opposite. You're supposed to be able to reuse objects that were moved from unless otherwise documented, although it may require reinitializing it explicitly. A moved from vector is valid to reuse. Although the standard doesn't specify what state it will be in, all major standard library implementations return it to the default constructed state which doesn't require an allocation.
Read what I wrote again. I said all member functions.
Reusing of a moved from object only requires assignment and destruction to be well behaved.
The std library containers give you extra guarantees (a moved from object is effectively the same as a default constructed one), but the _language_ imposes no such requirements on your types
It's perfectly allowed by the language for the .size() member of your own vector type to return a random value after it's been moved from because you wanted to save 1 CPU instruction somewhere.
I understand what you're saying but no major compiler does anything nefarious. They default initialize it. A lot of code depends on std::optional that was moved from returning false for is_valid for instance and no one would break that even if it's not guaranteed.
This is false. Moved-from is a "valid but unspecified" state in C++, so perfectly safe, but each type must decide what to do. At the minimum, the destructor must be able to run, because it will be invoked, meaning that the obvious choice is also the only sensible one: letting moved-from be equivalent to the "empty" state.
An empty std::vector does not require that any buffer is allocated. It just has a null data pointer.
It's not true that the only sensible choice for a moved-from object be equivalent to the defaulted constructed one.
If your move constructor doesn't exist then the copy constructor gets called under the language rules, so the sensible default is actually a copy.
Everything else is an optimisation that has a trade-off
A conforming implementation of std::list, for example, can have a default constructor and a move constructor that both allocate a sentinel node on the heap, which is why none of the constructors are noexcept.
If you don't allocate a sentinel on the heap, then moving std::list can invalidate iterators (which is what GNU stdlibc++'s implementation chooses).
I did not say “default-constructed”, because that’s a whole other can of worms.
But yes, the implication of C++ move semantics is that every movable object must also define an “empty” (moved-from) state, so you cannot have something like a never-null unique ptr.
Specifically, it is not allowed for the moved-from object to be inconsistent or to say “using it in any way is UB”, because its destructor will run.
You can't magically make all the member functions on std::vector safe after a move for example unless the moved from vector allocates itself a new (empty) buffer, which kills the performance benefits.
It's all by design.