That's partly true, partly circular. Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.
I don't think there is anything "partly" about it being true. A moved-from object is expected to remain valid and preserve class invariants. If you wrote a class whose objects fails to remain valid after being moved,you wrote bugs into your code.
> Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.
You are not required to implement an empty state. You are only required to write your classes so that after moving an object it remains valid. You are free to specify what this means to your classes, and can be anything from leaving the object as if it was default initialized or have literally a member variable such as bool moved. It's up to you. In C++'s perspective as long as your moved-from object can be safely destroyed them it's all good. Anything else is the behavior you chose to have, and bugs you introduced.
It’s… not a part of the langage which mandates a default ctor in the first place.
Why should it, tough? Think about it. The goal of move semantics is performance, mainly avoiding to copy/initialize expensive objects using a standard syntax. Why do you believe it would be a good idea to force constructors when they can very well be the reason why move should be used?
If you have any type that represents validated data, say a string wrapper which conveys (say) a valid customer address, how do you empty it out?
You could turn it into an empty string, but now that .street() method has to return an optional value, which defeats the purpose of your type representing validated data in the first place.
The moved-from value has to be valid after move (all of its invariants need to hold), which means you can’t express invariants unless they can survive a move.
It is much better for the language to simply zap the moved-from value out of existence so that you don’t have to deal with any of that.
Second, why can't the .street() method simply return an empty string in this case?
> The moved-from value has to be valid after move (all of its invariants need to hold)
The full quote from the C++ standard is: "Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state" AFAIK, it only makes such requirements for standard library types, but not for user defined types. Please correct me if I'm wrong.
The smart pointer std::unique_ptr<T> is an example of this, sometimes people will say it's basically a boxed T, so analogous to Rust's Box<T> but it isn't quite, it's actually equivalent to Option<Box<T>>. And if we don't want to allow None? Too bad, you can't express that in C++
But you're right that C++ people soldier on, there aren't many C++ types where this nonsense unavoidably gets in your face. std::variant's magic valueless_by_exception is such an example and it's not at all uncommon for C++ people to just pretend it can't happen rather than take it square on.
Again, these cases are still rare. Most classes either don't require user-defined move operations, or they have some notion of emptiness or default state.
> And if we don't want to allow None? Too bad, you can't express that in C++
That's actually a good example! Nitpick: you can express it in C++, just not without additional logic and some overhead :)