Date: Sat, 8 Aug 2020 22:20:36 -0400
On Sat, Aug 8, 2020 at 2:32 PM Walt Karas via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Date: Fri, 7 Aug 2020 21:22:45 -0400
> From: Jason McKesson <jmckesson_at_[hidden]>
> To: std-proposals_at_[hidden]
> Subject: Re: [std-proposals] Default assignment operators from
> copy/move constructors
> Message-ID:
> <CANLtd3WeOR0cxt37rs4c-AvuVYWD=q-ZndpFzOrQAECNdVnw=Q_at_[hidden]>
> Content-Type: text/plain; charset="UTF-8"
>
> On Fri, Aug 7, 2020 at 2:47 PM Walt Karas via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> >
> > On Friday, August 7, 2020, 1:33:44 PM CDT, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote: On Fri, 7 Aug 2020 at 21:21, Walt Karas via Std-Proposals<std-proposals_at_[hidden]> wrote:
> > >
> > > On Friday, August 7, 2020, 12:57:08 PM CDT, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote:
> > > On Fri, 7 Aug 2020 at 20:47, Walt Karas via Std-Proposals
> > > <std-proposals_at_[hidden]> wrote:
> > > > You can't do a destroy+placement-new if the placement-new can throw.
> > > >
> > > > WK: OK, also good point. If the constructor can throw, then conceptually the default assign can be like this: https://godbolt.org/z/jvdf53
> > >
> > > That approach doesn't work for anything non-trivial, and requires
> > > C++20 implicit object creation otherwise.
> > >
> > > WK: Hmm can you give quick example of a class for which this would not work? Or can you give a smaller reference than two long (and expensive) books?
> >
> >
> > struct X
> > {
> > std::string str;
> > };
> >
> > This type can *NOT* be memcpyed, and restoring it from raw bytes is
> > equally non-doable.
> >
> > WK: it's true that in general it can't be memcpyed. But, in this case we are guaranteed that only one of the copies will be destroyed, so it seems safe. In the generated assign, the raw data of the instance is copied off, and then copied back, to the original instance address before it is destoyed. I can't think of an example of a class for which this would not be safe.
>
> It doesn't matter if a compiler might successfully permit such code to
> "function". The C++ standard declares this code to exhibit undefined
> behavior, and that's unlikely to change anytime soon (not for types
> with non-trivial copies/moves, at any rate). The most you'd ever see
> is some form of destructive-move, and even that is going to, at some
> level, require explicit user intervention to enable for types with
> non-trivial copies/moves.
>
> WK: Compilers don't generate C++ they generate object code. The C++ code I gave is only supposed do describe how the generated object code would work.
... huh? I never suggested that compilers generate C++.
> This is not Standard-compliant code:
>
> void g(T &);
>
> void f(T &v)
> {
> char cpy[sizeof(T)];
> std::memcpy(cpy, &v, sizeof(T));
> std::memset(&v, 0, sizeof(T));
> std::memcpy(&v, cpy, sizeof(T));
> g(v);
> }
>
> But I can't think of an example of a type T where it wouldn't work. Can you?
I don't know what you mean by "work" in this context. This doesn't
"work" for *all* types `T` because, according to the C++ language,
that code is *nonsense*.
Again, it may be the case that the undefined behavior exhibited by
this code will be the behavior you desire or even expect. This is no
different from being able to figure out what "sentence no words
English" means. But that doesn't make it legitimate English, and
similarly the code here is not reasonable C++ code. So it doesn't
matter if it would "work" by some definition; the rules say that it's
gibberish.
What I think you're trying to get at is to ask why this should be
forbidden. Why should a user be prevented from storing the bytes of an
object somewhere, nulling out the bytes of that object, and then
restoring them to their original value?
I'm going to ignore anything that has to do with concurrent accesses
to the object referenced by `v` (or similar). Let's look at this
solely from a single-threaded perspective.
It should be forbidden because it doesn't make any kind of sense. Not
just in the "the C++ standard declares this to be UB" sense, but in a
general "what does the word 'object' mean?" sense.
It does not make sense to be able to arbitrarily force any particular
state onto *every* object. It may make sense for some objects. But an
object should be able to define its own concept of validity. And the
validity of an object throughout its lifetime should be able to be
preserved across all *well-defined* C++ operations. So an arbitrary
object should not be able to be put into an invalid state, not even as
an intermediary between two valid states.
As such, direct modification of an object's value representation
should be restricted to operations which always put the object in a
state that is known to be valid. That is, bytewise copying from
existing objects (or from bytes copied from existing objects). And
such operations should only be valid for types for which a standard
copy operation must be equivalent to a bytewise copy.
More lenient rules start us down the road towards "object" no longer
meaning anything. To just having typed pointers and references to
bytes. And while I know that's a road that some people want to go
down, the road we're currently on allows for useful things like static
analysis, lifetime analysis for objects, and so forth.
<std-proposals_at_[hidden]> wrote:
>
> Date: Fri, 7 Aug 2020 21:22:45 -0400
> From: Jason McKesson <jmckesson_at_[hidden]>
> To: std-proposals_at_[hidden]
> Subject: Re: [std-proposals] Default assignment operators from
> copy/move constructors
> Message-ID:
> <CANLtd3WeOR0cxt37rs4c-AvuVYWD=q-ZndpFzOrQAECNdVnw=Q_at_[hidden]>
> Content-Type: text/plain; charset="UTF-8"
>
> On Fri, Aug 7, 2020 at 2:47 PM Walt Karas via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> >
> > On Friday, August 7, 2020, 1:33:44 PM CDT, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote: On Fri, 7 Aug 2020 at 21:21, Walt Karas via Std-Proposals<std-proposals_at_[hidden]> wrote:
> > >
> > > On Friday, August 7, 2020, 12:57:08 PM CDT, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote:
> > > On Fri, 7 Aug 2020 at 20:47, Walt Karas via Std-Proposals
> > > <std-proposals_at_[hidden]> wrote:
> > > > You can't do a destroy+placement-new if the placement-new can throw.
> > > >
> > > > WK: OK, also good point. If the constructor can throw, then conceptually the default assign can be like this: https://godbolt.org/z/jvdf53
> > >
> > > That approach doesn't work for anything non-trivial, and requires
> > > C++20 implicit object creation otherwise.
> > >
> > > WK: Hmm can you give quick example of a class for which this would not work? Or can you give a smaller reference than two long (and expensive) books?
> >
> >
> > struct X
> > {
> > std::string str;
> > };
> >
> > This type can *NOT* be memcpyed, and restoring it from raw bytes is
> > equally non-doable.
> >
> > WK: it's true that in general it can't be memcpyed. But, in this case we are guaranteed that only one of the copies will be destroyed, so it seems safe. In the generated assign, the raw data of the instance is copied off, and then copied back, to the original instance address before it is destoyed. I can't think of an example of a class for which this would not be safe.
>
> It doesn't matter if a compiler might successfully permit such code to
> "function". The C++ standard declares this code to exhibit undefined
> behavior, and that's unlikely to change anytime soon (not for types
> with non-trivial copies/moves, at any rate). The most you'd ever see
> is some form of destructive-move, and even that is going to, at some
> level, require explicit user intervention to enable for types with
> non-trivial copies/moves.
>
> WK: Compilers don't generate C++ they generate object code. The C++ code I gave is only supposed do describe how the generated object code would work.
... huh? I never suggested that compilers generate C++.
> This is not Standard-compliant code:
>
> void g(T &);
>
> void f(T &v)
> {
> char cpy[sizeof(T)];
> std::memcpy(cpy, &v, sizeof(T));
> std::memset(&v, 0, sizeof(T));
> std::memcpy(&v, cpy, sizeof(T));
> g(v);
> }
>
> But I can't think of an example of a type T where it wouldn't work. Can you?
I don't know what you mean by "work" in this context. This doesn't
"work" for *all* types `T` because, according to the C++ language,
that code is *nonsense*.
Again, it may be the case that the undefined behavior exhibited by
this code will be the behavior you desire or even expect. This is no
different from being able to figure out what "sentence no words
English" means. But that doesn't make it legitimate English, and
similarly the code here is not reasonable C++ code. So it doesn't
matter if it would "work" by some definition; the rules say that it's
gibberish.
What I think you're trying to get at is to ask why this should be
forbidden. Why should a user be prevented from storing the bytes of an
object somewhere, nulling out the bytes of that object, and then
restoring them to their original value?
I'm going to ignore anything that has to do with concurrent accesses
to the object referenced by `v` (or similar). Let's look at this
solely from a single-threaded perspective.
It should be forbidden because it doesn't make any kind of sense. Not
just in the "the C++ standard declares this to be UB" sense, but in a
general "what does the word 'object' mean?" sense.
It does not make sense to be able to arbitrarily force any particular
state onto *every* object. It may make sense for some objects. But an
object should be able to define its own concept of validity. And the
validity of an object throughout its lifetime should be able to be
preserved across all *well-defined* C++ operations. So an arbitrary
object should not be able to be put into an invalid state, not even as
an intermediary between two valid states.
As such, direct modification of an object's value representation
should be restricted to operations which always put the object in a
state that is known to be valid. That is, bytewise copying from
existing objects (or from bytes copied from existing objects). And
such operations should only be valid for types for which a standard
copy operation must be equivalent to a bytewise copy.
More lenient rules start us down the road towards "object" no longer
meaning anything. To just having typed pointers and references to
bytes. And while I know that's a road that some people want to go
down, the road we're currently on allows for useful things like static
analysis, lifetime analysis for objects, and so forth.
Received on 2020-08-08 21:24:06