On 24 Apr 2025 19:45, Jonathan Wakely wrote:
>
> In a language where the compiler does not (and in general can not)
> prevent you from using a moved-from object, it isn't a good idea to
> make moved-from objects radioactive and unsafe. The reason Jon's
> argument doesn't make sense for me is because ... this is C++. We
> don't have a lifetime-checker or borrow-checker that would allow his
> desired model to work.
>
> Frankly, I struggled to find any part of the talk I agreed with.
>
> The ViewPort example is just dumb, nothing says he can't implement the
> cheap move. He offers two bad choices, and misses the obvious good
> solution. All you need is a ViewPort::valid() member that is false
> after being moved-from and then say valid() is a precondition for the
> other operations on it. What's so hard about that?
How would that change the practical state of things, compared to what
Jon suggests? Ok, you have your valid() function that returns false for
a moved-from object. You still are not allowed to call any methods on
the ViewPort except destructor and assignment because now you have
valid() as a precondition everywhere. So you're back to square one, only
added a new valid() method that may be never needed by the user or the
ViewPort implementer.
I understand that the difference is in the formal contract with the
user, which is more explicitly defined.
Which is an improvement.
But functionally preconditions
"valid() returns true" and "not in moved-from state" are equivalent.
One is testable and can be used to build reliable code (e.g. add assertions to check preconditions, or check if an object is invalid and allocate new resources if needed) and one is a radioactive object that you just have to use correctly and never make mistakes. When the testable version has zero runtime overhead, what justification is there for the other option?
Neither option is safer than the other as the compiler won't
automatically test for valid(). That is unless ViewPort implementer adds
asserts or otherwise checks for the moved-from state everywhere, which
he could do with or without valid().
Users can do the checks for one model, and only the implementation can do it for the other.
> Nothing in the standard's "valid but unspecified" model says types
> can't have preconditions on some operations, e.g. std::unique_ptr is
> empty after being moved-from, and that means you can't use operator*
> on it. But that's fine, because being empty is a valid state.
The difference with the standard library is very real. std::list doesn't
define any equivalent of valid() and also doesn't have a moved-from
state,
Of course it has a moved-from state. You mean it didn't have a partially-formed state that could be used as the moved-from state.
which makes its move constructor potentially throwing.
No, that's not what makes it potentially throwing. *One particular* implementation of std::list has a potentially throwing move, but the reason is backwards compatibility, not because of some inherent limitation of std::list or any requirement in the standard.
The MSVC std::list and libstdc++ std::deque are relics from an older time. I wish they could be changed, but they can't (but people seem to forget that their suboptimal moves are still cheaper than copies!).
"Some very rare types with very particular backwards compatibility constraints are suboptimal" is not a good argument for ... anything. Nobody is arguing that those types are good designs for how to implement move semantics. Nobody is saying that's how you should implement your own types, in fact Thiago has already correctly argued the opposite: the default constructor should be cheap and non-throwing, and that same state can be used for the moved-from state.
You don't need partially-formed states, just cheap non-throwing default constructors.