C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Idea: moved_from<T>() for Efficient Moved-From State Construction

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Thu, 24 Apr 2025 17:45:16 +0100
On Thu, 24 Apr 2025 at 16:04, Andrey Semashev via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> On 24 Apr 2025 17:47, Jason McKesson via Std-Proposals wrote:
> > On Thu, Apr 24, 2025 at 7:07 AM Andrey Semashev via Std-Proposals
> > <std-proposals_at_[hidden]> wrote:
> >>
> >> On 24 Apr 2025 13:55, Ville Voutilainen wrote:
> >>> On Thu, 24 Apr 2025 at 13:48, Andrey Semashev via Std-Proposals
> >>> <std-proposals_at_[hidden]> wrote:
> >>>>> No, a moved-from std::list also needs to support all operations that
> >>>>> have no preconditions, such as size() and empty() and begin() and end().
> >>>>
> >>>> Well, likewise, citation needed. As far as I know, the standard doesn't
> >>>> formally define requirements on the moved-from objects.
> >>>
> >>> Yes, it does. Your citation is [defns.valid]:
> >>>
> >>> 〈library〉 value of an object that is not specified except that the
> >>> object’s invariants are met and operations on
> >>> the object behave as specified for its type
> >>> [Example 1 : If an object x of type std::vector<int> is in a valid but
> >>> unspecified state, x.empty() can be called
> >>> unconditionally, and x.front() can be called only if x.empty() returns
> >>> false. — end example]
> >>
> >> Ok, so the standard library makes extra guarantees about the types it
> >> provides. It harms e.g. std::list and I don't agree with this approach,
> >> but fine.
> >>
> >> It doesn't make a general rule about moved-from objects, including not
> >> provided by the standard library. And it doesn't invalidate what I said
> >> in my post or undermine the usefulness of the proposal.
> >
> > The standard cannot tell you what to do with your objects. It's valid
> > C++ code to make the moved-from state only permit assignment or
> > destruction.
> >
> > The question is what the general prescribed practice is within the C++
> > community and what actually gets used in commonly used projects. For
> > example, the C++ core guidelines suggest that people should adopt the
> > standard's rules for moved-from objects.
>
> Someone sent me a link to Jon Kalb's talk[1] (many thanks!) where he
> discusses this issue. He presents an example with a wrapper for a
> ViewPortID handle, and I think that example illustrates very well that
> this approach is generally not practical. And that is not an entirely
> made up example, as I've had similar cases in my practice as well.

I take issue with several of the points in this talk. Describing
vector::operator[] vs vector::at as unchecked vs checked is
misleading, it's not the case. In practice, they're both checked in
many cases, even in lots of production code. The difference is that
vector::at throws exceptions, and *that* is what makes it slower. The
dichotomy is "terminate vs throw" not "unchecked vs checked".

He claims that checking inside vector::operator[] is almost always
redundant (because the caller has checked) and so it results in wasted
cycles doing redundant checking. But the fact they're redundant is
precisely what makes those checks cheap and in many cases **almost
zero overhead**.

He keeps claiming in the first 15 minutes that making the language
compromises performance, with no evidence, just feels. So I take issue
with the premise that seems to be "unsafe by default, fast at all
costs, because this is C++".

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? There are **no
performance implications**. Nothing says you need to ever test that
valid() member anywhere in the other operations, because it's just a
precondition that the caller is required to ensure is met. If the
caller knows they aren't passing a moved-from object, they don't need
to check valid() at the call sites, and the callee doesn't need to
check (but could optionally have assertions to help debugging).
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.

Received on 2025-04-24 16:45:32