Date: Thu, 24 Apr 2025 20:25:59 +0100
On Thu, 24 Apr 2025, 20:18 Jonathan Wakely, <cxx_at_[hidden]> wrote:
>
>
> On Thu, 24 Apr 2025, 19:27 Andrey Semashev via Std-Proposals, <
> std-proposals_at_[hidden]> wrote:
>
>> 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.
>
And it doesn't *need* to be the default constructor, if there's some other
way to get a cheap but valid state, like the nullopt_t constructor for
optional.
A default constructor is often a sensible choice for that cheap (but valid)
state, but it could be some other constructor. But that should just be
considered an empty state, not a partially formed state, and there's rarely
a reason to have a partially formed state that can't be tested for.
> Pointing to an example of a type that gets that wrong is not a good
> argument for a fundamentally more error prone programming model.
>
>
>
>
>
> On Thu, 24 Apr 2025, 19:27 Andrey Semashev via Std-Proposals, <
> std-proposals_at_[hidden]> wrote:
>
>> 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.
>
And it doesn't *need* to be the default constructor, if there's some other
way to get a cheap but valid state, like the nullopt_t constructor for
optional.
A default constructor is often a sensible choice for that cheap (but valid)
state, but it could be some other constructor. But that should just be
considered an empty state, not a partially formed state, and there's rarely
a reason to have a partially formed state that can't be tested for.
> Pointing to an example of a type that gets that wrong is not a good
> argument for a fundamentally more error prone programming model.
>
>
>
Received on 2025-04-24 19:26:16