lmi
[Top][All Lists]
Advanced

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

Re: [lmi] Detecting whether move semantics actually take place


From: Vadim Zeitlin
Subject: Re: [lmi] Detecting whether move semantics actually take place
Date: Mon, 1 Aug 2022 00:54:12 +0200

On Sun, 31 Jul 2022 21:16:15 +0000 Greg Chicares <gchicares@sbcglobal.net> 
wrote:

GC> Thus, my top-level (mis)understanding was that the most-derived class's
GC> move operations look for impediments

 I'd like to inject a remark here: there is a difference between the
default, compiler-generated move special members and the manually defined
ones. Default ones do what you'd expect them to, i.e. use the corresponding
move ctors or assignment operators of the bases/members. But the
user-defined move ctor can do whatever it wants (e.g. print "copied", even
if it doesn't make any sense, as my example shows!), including _not_ using
the unavailable bases/members move members. This is quite rare IME, but you
can inherit from a non-movable class and still implement a move ctor in the
derived one if you really want to.

GC> Here, I believe you meant the copy ctor to print "copied":

 Yes, of course, sorry for this very confusing typo. It would be great to
be able to say that I made it to check if you were paying attention, but I
didn't do it intentionally at all (and, besides, I know that you always pay
attention to things like that anyhow!), sorry again.

GC> > struct move_detector {
GC> >     explicit move_detector(int n) : n_{n} {}
GC> >     move_detector(move_detector const& x) : n_{x.n_} { printf("[%d] 
moved\n", n_); }
GC>                                                                       ^^^^^
GC>                                                                       copied
GC> >     move_detector(move_detector&& x) : n_{x.n_} { printf("[%d] moved\n", 
n_); }
GC> > 
GC> >     int n_;
GC> > };
GC> 
GC> [with that change, it still prints "moved" in your example]

 It would have been even more unfortunate if it didn't...

GC> Let's use that to test the hypothesis above. I'll modify your example,
GC> transplanting the move_detector member to the base class, and changing the
GC> prime number so there's no question whether I'm running the new 'a.out':
GC> 
GC> ---------------------------------- >8 --------------------------------------
GC> #include <stdio.h>
GC> #include <utility>
GC> 
GC> struct move_detector {
GC>     explicit move_detector(int n) : n_{n} {}
GC>     move_detector(move_detector const& x) : n_{x.n_}
GC>         { printf("[%d] copied\n", n_); }
GC>     move_detector(move_detector&& x) : n_{x.n_}
GC>         { printf("[%d] moved\n", n_); }
GC> 
GC>     int n_;
GC> };
GC> 
GC> struct base {
GC>     ~base() = default;
GC> 
GC>     move_detector value{19};
GC> };
GC> 
GC> struct non_movable : base {
GC>     non_movable() = default;
GC>     non_movable(non_movable const&) = default;
GC>     non_movable(non_movable&&) = default;
GC> };
GC> 
GC> int main() {
GC>     non_movable x;
GC>     return non_movable{std::move(x)}.value.n_;
GC> }
GC> ---------------------------------- >8 --------------------------------------
GC> 
GC> I predict that move_detector will be copied, so 'a.out' will print
GC> "[19] copied" and return 19. Let's see:
GC> 
GC> /opt/lmi/src/lmi[0]$clang -Wall -std=c++20 eraseme.cpp && ./a.out || echo $?
GC> [19] copied
GC> 19
GC> 
GC> Hypothesis confirmed, or, at least, not disproven.

 Yes, base doesn't have a move ctor due to the presence of the dtor, but
the implicitly generated non_movable move ctor can, and does, still use its
copy ctor.

GC> Can it flip back and forth both ways? Let's try:
GC> 
GC> ---------------------------------- >8 --------------------------------------
GC> #include <stdio.h>
GC> #include <utility>
GC> 
GC> struct move_detector {
GC>     explicit move_detector(int n) : n_{n} {}
GC>     move_detector(move_detector const& x) : n_{x.n_}
GC>         { printf("[%d] copied\n", n_); }
GC>     move_detector(move_detector&& x) : n_{x.n_}
GC>         { printf("[%d] moved\n", n_); }
GC> 
GC>     int n_;
GC> };
GC> 
GC> struct base0 {
GC>     base0() = default;
GC>     base0(base0 const&) = delete;
GC>     base0(base0&&) = default;
GC>     move_detector value{13};
GC> };
GC> 
GC> struct base1 : base0 {
GC>     ~base1() = default;
GC> };
GC> 
GC> struct non_movable : base1 {
GC>     non_movable() = default;
GC>     non_movable(non_movable const&) = default;
GC>     non_movable(non_movable&&) = default;
GC> };
GC> 
GC> int main() {
GC>     non_movable x;
GC>     return non_movable{std::move(x)}.value.n_;
GC> }
GC> ---------------------------------- >8 --------------------------------------
GC> 
GC> Wow.

 Sorry, I'm not sure what do you mean here... For me the result (failure to
compile) makes sense.

GC> I just wish clang or gcc had a switch to pretend that the Committee
GC> had been more bold and overturned the implicit-copy rules.

 I think this would break so much existing code that it would be completely
impractical. You really need to go the Carbon route and create an entire
new language without backwards compatibility (but with interoperability,
because otherwise why bother using at all) with C++ to solve this.

GC> >  So AFAICS everything works fine here and clang 
-Wdefaulted-function-deleted
GC> > is given when you'd expect it to be, i.e. when the defaulted function is
GC> > actually deleted.
GC> 
GC> Yes. It's not a manifest error, yet, but it's better to get an early 
warning,
GC> which gcc doesn't give (at least not with only '-Wall'):

 Yes, I'm not sure why it doesn't, as it seems like a very useful warning.
I didn't try to find it, but my completely wild guess would be that it's
less simple to implement in gcc than in clang because if it were simple to
add, it should really have been done a long time ago.

 Regards,
VZ

Attachment: pgpjNe9ZQaeRk.pgp
Description: PGP signature


reply via email to

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