lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Is this class moveable or not?


From: Vadim Zeitlin
Subject: Re: [lmi] Is this class moveable or not?
Date: Wed, 13 Jul 2022 14:00:47 +0200

On Wed, 13 Jul 2022 00:06:28 +0000 Greg Chicares <gchicares@sbcglobal.net> 
wrote:

GC> On 7/12/22 21:40, Vadim Zeitlin wrote:
GC> > On Tue, 12 Jul 2022 16:18:34 +0000 Greg Chicares 
<gchicares@sbcglobal.net> wrote:
GC> > 
GC> > GC> On 7/11/22 22:12, Vadim Zeitlin wrote:
GC> > GC> > On Mon, 11 Jul 2022 16:22:14 +0000 Greg Chicares 
<gchicares@sbcglobal.net> wrote:
GC> [... can't snip the diff because it's referred to below ...]
GC> > GC> > GC> > GC> diff --git a/gpt_server.hpp b/gpt_server.hpp
GC> > GC> > GC> > GC> index cafeb177..f911087f 100644
GC> > GC> > GC> > GC> --- a/gpt_server.hpp
GC> > GC> > GC> > GC> +++ b/gpt_server.hpp
GC> > GC> > GC> > GC> @@ -48,6 +48,12 @@ class LMI_SO gpt_server final
GC> > GC> > GC> > GC>    public:
GC> > GC> > GC> > GC>      explicit gpt_server(mcenum_emission);
GC> > GC> > GC> > GC>  
GC> > GC> > GC> > GC> +    gpt_server(gpt_server const&) = default;
GC> > GC> > GC> > GC> +    gpt_server(gpt_server&&) = default;
GC> > GC> > GC> > GC> +    gpt_server& operator=(gpt_server const&) = delete;
GC> > GC> > GC> > GC> +    gpt_server& operator=(gpt_server&&) = delete;
GC> > GC> > GC> > GC> +    ~gpt_server() = default;
[...]
GC> Interesting. The diff above, just to be clear, didn't attempt to suggest
GC> whether the class should be movable or not; it's merely the result of:
GC>  - writing all the move and copy special members with "= delete", and then
GC>  - changing "= delete" to "= default" if the compiler complained.
GC> I had taken that to mean that the move ctor was required. However...

 Yes, you were right, sorry, I didn't realize that using it with for_each()
was incompatible with having a deleted move ctor.

GC> Let's test that hypothesis by applying this patch to e998b52376e17, which
GC> I pushed a couple hours ago:
GC> 
GC> --8<----8<----8<----8<----8<----8<----8<----8<----8<--
GC> diff --git a/gpt_server.hpp b/gpt_server.hpp
GC> index 524be42e2..f41f1ae96 100644
GC> --- a/gpt_server.hpp
GC> +++ b/gpt_server.hpp
GC> @@ -49,9 +49,9 @@ class LMI_SO gpt_server final
GC>      explicit gpt_server(mcenum_emission);
GC>  
GC>      gpt_server(gpt_server const&) = default;
GC> -    gpt_server(gpt_server&&) = default;
GC> -    gpt_server& operator=(gpt_server const&) = default;
GC> -    gpt_server& operator=(gpt_server&&) = default;
GC> +    gpt_server(gpt_server&&) = delete;
GC> +    gpt_server& operator=(gpt_server const&) = delete;
GC> +    gpt_server& operator=(gpt_server&&) = delete;
GC>      ~gpt_server() = default;
GC>  
GC>      bool operator()(fs::path const&);
GC> --8<----8<----8<----8<----8<----8<----8<----8<----8<--
GC> 
GC> MinGW-w64 gcc-10 compiles and links that,

 This is surprising. I don't see any differences between std::for_each()
implementation in 10 and 11, so it looks like a real compiler change/bug
fix.

GC> but GNU/Linux gcc-11 doesn't:
[...]
GC> and neither does clang-13:
[...]

 Yes, because std::for_each() returns an object of gpt_server type. I
had missed it when looking at the code and decided that it was unlikely
that we would need to return this object from any function ourselves, which
is why I thought it might not need to be movable.

GC> That doesn't confirm the hypothesis that the move ctor is required,
GC> or even that it would actually be used; it just says that I declared
GC> it, so it participates in overload resolution, which prefers it,
GC> but it was explicitly deleted.

 Yes.

GC> But now if I comment it out:
GC> 
GC> -    gpt_server(gpt_server&&) = delete;
GC> +//  gpt_server(gpt_server&&) = delete;
GC> 
GC> then all compilers accept it. The original hypothesis is rejected:
GC> the code can work with no move ctor, as long as we take care not to
GC> declare it at all (which is not the same as declaring it as deleted).

 Yes, but is the same as using "= default".

GC> But here's a surprise: what will the following patch print?
GC> (Revert any earlier experimental change and apply it to HEAD.)
GC> We speculated above that the class is non-movable,

 Sorry, I think you're mixing things up here. In the test above the class
was non-movable because you had explicitly deleted its move ctor. In the
test below it's very clearly *is* movable because you've defined the move
ctor yourself.

GC> so our new hypothesis is that it'll print "copied":

 No, absolutely not. We expect our move ctor to be used, of course. All the
considerations about whether a class is movable or not only apply in the
absence of an explicitly defined (or deleted) move ctor. Deleting it makes
the class non-movable even if it would have been movable by default.
Defining it makes the class movable even if it wouldn't have been movable
by default.

[...expected result snipped...]
GC> Does that falsify the hypothesis that this class cannot be moved?

 It cannot be moved if you don't define a move ctor. If you do define it,
it can, tautologically, be moved.

GC> > GC> or does the language quietly substitute copy semantics for that member
GC> > GC> so that a defaulted move of the owning class is valid?
GC> 
GC> Here, I meant:
GC> 
GC>   for each data member M
GC>      if M is moveable, move it
GC>      else copy it
GC> 
GC> >  Yes, but perhaps not in the way you meant it: initializing gpt_server 
from
GC> > an rvalue reference to gpt_server will use copy, but just because it will
GC> > use its copy ctor. This happens because "= default" on the move 
constructor
GC> > above actually deletes it, because it can't be implemented, and such
GC> > deleted-due-to-default move ctor is ignored by the overload resolution, so
GC> > the copy ctor will be found and used instead.
GC> 
GC> Here, I think you're saying:
GC> 
GC>   if all data members are moveable, move them all
GC>   else copy all data members

 Yes.

GC> and that there exists one non-moveable data member, so the defaulted
GC> move ctor is "defined as deleted" [class.copy.assign/7].

 Yes. But, importantly, it's also excluded from consideration by the
overload resolution in this case.

GC> Does my demonstration above persuade you otherwise?

 No, not at all.

GC> Or does it actually prove nothing, for some reason
GC> that transcends my rather limited understanding?

 The reason is very simple: defining your own move ctor overrides all the
other considerations. You're completely changing the situation you're
observing by doing it.

 If you really want to check what happens when gpt_server is used by
for_each you'd need to add to it a member whose copy ctor prints "copied"
and move ctor prints "moved" (without making any changes to gpt_server
itself). If you do this, I confidently predict -- so confidently that I
didn't even test it -- that it will print "copied" because gpt_server has
no move ctor, due to gpt_state not having one.


 To summarize:

- If you define a move ctor for a class, it is, almost by definition,
  movable and it doesn't matter what base classes and members it has
  (this may well matter for the move ctor implementation, of course).

- If you delete a move ctor in a class, it is not movable.

- If you default the move ctor or if your class doesn't have neither copy
  ctor nor dtor, then the availability of the move ctor depends on whether
  its base class and members are movable.

- Otherwise it is not movable.

 Does this make sense?
VZ

Attachment: pgphqDP9a1cjm.pgp
Description: PGP signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]