Topic: A suggestion to enhance the move semantics proposal n1377


Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Thu, 28 Jul 2005 17:13:20 GMT
Raw View
On 2005-07-28 04:43, Pablo Halpern wrote:
> Howard Hinnant <hinnant@metrowerks.com> wrote:
>
>>Your proposed swap swells memory usage by a factor of 2 during the swap.
>>In a resource constrained environment this makes a failure more likely.
>
> This is true in the case of unequal allocators, but there are mitigating
> factors.  Please see my longer response in the thread, "Revised
> Allocator Proposal". (I wish there were a way to link threads in net
> news.

Linking to the first message in the thread generally does the trick.
In this case: news:7ibbe1hu4omoqsfibanas46idf9u6i5hjo@4ax.com

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Pablo Halpern <phalpern@halpernwightsoftware.org>
Date: Tue, 26 Jul 2005 10:37:07 CST
Raw View
hinnant@metrowerks.com (Howard Hinnant) wrote:

>
>The immediate application is std::swap:
>
>template <class T>
>inline
>void
>swap(T& a, T& b)
>{
>    T tmp(std::move(a));
>    a = std::move(b);
>    b = std::move(tmp);
>}
>

Pardon the interruption, but I just want to call attention to the fact
that not everybody agrees that swap should be implemented as a set of
move operations.  In my allocator proposal, I argue that, even though it
might be slower, it is conceptually cleaner and safer for swap to
exchange *values*, not *objects*.  I propose a separate swap_move
algorithm that could be implemented as above.  See my proposal (updated
today) for more details:
http://home.earthlink.net/~phalpern/cpp/HalpernAllocatorProposal.pdf .

I now return you to the Usenet thread in progress...

Pablo Halpern                 phalpern@halpernwightsoftware.com
Author: The C++ Standard Library from Scratch
http://www.halpernwightsoftware.com/stdlib-scratch

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Howard Hinnant <hinnant@metrowerks.com>
Date: Tue, 26 Jul 2005 15:58:09 CST
Raw View
In article <ugabe1l3r1r4nv2ctt868juonkeqg69bir@4ax.com>,
 Pablo Halpern <phalpern@halpernwightsoftware.org> wrote:

> hinnant@metrowerks.com (Howard Hinnant) wrote:
>
> >
> >The immediate application is std::swap:
> >
> >template <class T>
> >inline
> >void
> >swap(T& a, T& b)
> >{
> >    T tmp(std::move(a));
> >    a = std::move(b);
> >    b = std::move(tmp);
> >}
> >
>
> Pardon the interruption, but I just want to call attention to the fact
> that not everybody agrees that swap should be implemented as a set of
> move operations.  In my allocator proposal, I argue that, even though it
> might be slower, it is conceptually cleaner and safer for swap to
> exchange *values*, not *objects*.  I propose a separate swap_move
> algorithm that could be implemented as above.  See my proposal (updated
> today) for more details:
> http://home.earthlink.net/~phalpern/cpp/HalpernAllocatorProposal.pdf .

Your proposed swap swells memory usage by a factor of 2 during the swap.
In a resource constrained environment this makes a failure more likely.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Howard Hinnant <hinnant@metrowerks.com>
Date: Tue, 26 Jul 2005 21:16:47 CST
Raw View
In article <42E10214.9040701@moderncppdesign.com>,
 seewebsiteforemail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website for Email)") wrote:

> Howard Hinnant wrote:
> > Also, expand-in-place is not just a:
> >
> >     Double my allocation or else
> >
> > proposition.  Rather it goes more like:
> >
> > Client: Can you double the size of my memory block without moving it?
> > malloc: No, but I can expand your 1000 byte block to 1500 bytes. Would
> >         you like me to do that?
> > Client: Sure, I can make use of the extra 500 bytes, thanks.
> >
> > This provides extreme flexibility and efficiency to vector.
>
> Yah, we've talked about this protocol before. But I think it's more
> like this (due to concurrency issues):
>
> Client: Can you double the size of my memory block without moving it?
> malloc: No, but I can expand your 1000 byte block to 1500 bytes.
> **Here you go, I've done it for ya, otherwise some other dude could
> allocate in another thread that memory and render me unable to respect
> my offer.**
> Client: Sure, I can make use of the extra 500 bytes, thanks.
> malloc: Say, what do you do after hours? Maybe we can drink some drain
> current together.
>
> No?

I don't think that interface is superior (maybe besides the drain
current thing ;-) ).

>From malloc's perspective it wants to satisfy as many threads as
possible.  Imagine two clients A & B:

A:  Can you double the size of my memory block without moving it?
malloc: No but I can give you a smaller amount and here it is.
B:  Can you give me N bytes of memory?
malloc:  Sorry, no, just gave my last bit of memory to A.
A:  Thanks anyway, but that won't work for me.  Take it back.
malloc:  *#%! I just had to turn B away for nothing!

A's request is represented by the function:

int resize_alloc(void* ptr, size_t size_requested,
                            size_t* size_received);

described here:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm#resize_alloc

However there is also an additional interface for A:

A:  Can you double the size of my memory block without moving it?  Or at
the very least, give me 4 more bytes?

That is the idea behind:

int negotiate_alloc(void* ptr, size_t min_size,
                               size_t preferred_size,
                               size_t* size_received);

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm#negotiate_alloc

So your next question might be:  Well doesn't negotiate_alloc make
resize_alloc redundant?

Not quite.  While std::vector may always prefer to use negotiate_alloc
over resize_alloc, other clients might not.  realloc is such a client.
It wants to know if a block can be resized to a specific size, and does
not want to negotiate a range.  True it could have called
negotiate_alloc with min_size == preferred_size, but that seems a little
clunky.

Also the two functions behave differently when attempting to shrink a
memory block (which vector is never allowed to do in the first place).
resize_alloc will attempt to shrink a block from its current size down
to size_requested, and if it is unable to lower the block size at all
will report failure.  negotiate_alloc can be used to shrink a memory
block, but that is a non-intuitive use for this function.  It always
reports success if the final memory block size is >= min_size (whether
or not any growing or shrinking actually took place).

In a sense, resize_alloc is really a more basic interface than
negotiate_alloc.  Indeed the paper shows how negotiate_alloc can be
built on top of resize_alloc with no compromises.  The main reason for
negotiate_alloc to exist is to collect the multiple calls to
resize_alloc required for the negotiation inside of the heap's mutex.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website for Email)" <seewebsiteforemail@moderncppdesign.com>
Date: 27 Jul 2005 05:10:09 GMT
Raw View
Howard Hinnant wrote:
> In article <42E10214.9040701@moderncppdesign.com>,
>  seewebsiteforemail@moderncppdesign.com ("Andrei Alexandrescu (See
>  Website for Email)") wrote:
>>Client: Can you double the size of my memory block without moving it?
>>malloc: No, but I can expand your 1000 byte block to 1500 bytes.
>>**Here you go, I've done it for ya, otherwise some other dude could
>>allocate in another thread that memory and render me unable to respect
>>my offer.**
>>Client: Sure, I can make use of the extra 500 bytes, thanks.
>>malloc: Say, what do you do after hours? Maybe we can drink some drain
>>current together.
>>
>>No?
>
>
> I don't think that interface is superior (maybe besides the drain
> current thing ;-) ).
 >
>>From malloc's perspective it wants to satisfy as many threads as
> possible.  Imagine two clients A & B:
>
> A:  Can you double the size of my memory block without moving it?
> malloc: No but I can give you a smaller amount and here it is.
> B:  Can you give me N bytes of memory?
> malloc:  Sorry, no, just gave my last bit of memory to A.
> A:  Thanks anyway, but that won't work for me.  Take it back.
> malloc:  *#%! I just had to turn B away for nothing!

First off, I understand this is a discussion with little, if any,
practical outcome since the alea have been jacta and all that stuff.
But if your post characterizes the interface additions completely, I
believe the spec is wanting.

So, about the scenario above: that unhappy scenario is of very low
occurrence, and I contend it illustrates a weakness of the system. The
following conditions must be at the same time true:

1. Thread A asks for size doubling of a chunk of M bytes, and is
granted only m < M bytes to the end of current block.

2. Thread B asks for N < m bytes during the small quantum of time
between 1 and 4 (below).

3. There is not enough contiguous memory to satisfy request for m bytes.

4. Thread A can't deal with M + m memory, but somehow can deal with M
memory, because otherwise the system would crash since there's no
chance to allocate 2 * M bytes anywhere (see 3).

I'd say this is as unlikely a scenario as to not warrant any more
discussion.

> A's request is represented by the function:
>
> int resize_alloc(void* ptr, size_t size_requested,
>                             size_t* size_received);
>
> described here:
>
> http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm#resize_alloc

Ok, so the interesting tidbit of information is: "On failure
*size_received holds a hint indicating a suggested size_requested that
might succeed in a future call."

That suggests a polling model for expanding "N bytes or to the end of
current block", which I believe should be the most common request.
There's no direct interface for that simple request?!? I need to write:

// max: upon input, the max size asked for
//      upon return, the actual size granted
void expand_to_eob(void* ptr, size_t & max) {
   while (resize_alloc(ptr, max, &max) == 0) {
   }
}

(There's no aliasing of max because the call makes a copy of it in the
2nd argument.) I think it's awkward to ask the user to implement this
function, albeit short, by hand. The wording is vague enough to give
no guarantee it will ever finish. Practically this is a form of
optimistic concurrency control that at best is handled within the
standard library. And again, I believe this is what a ton of
applications will want. In particular, std::vector will want exactly
this: double, or else gimme all you have.

I am disappointed. :o(

> However there is also an additional interface for A:
>
> A:  Can you double the size of my memory block without moving it?  Or at
> the very least, give me 4 more bytes?
>
> That is the idea behind:
>
> int negotiate_alloc(void* ptr, size_t min_size,
>                                size_t preferred_size,
>                                size_t* size_received);
>
> http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm#negotiate_alloc

I think that doesn't do much to liven up the mood.

> So your next question might be:  Well doesn't negotiate_alloc make
> resize_alloc redundant?

No. My next question is: how in the world is expand_to_eob not part of
the specification?

> Not quite.  While std::vector may always prefer to use negotiate_alloc
> over resize_alloc, other clients might not.  realloc is such a client.

I agree about realloc, I disagree about vector. When using resize(),
negotiate_alloc sounds like a good choice for vector. When
reallocating as the result of push_back, I think the vector's
proposition is simple: double the allocation or take all of the
current block so there's no more need later to pay for the interthread
exclusion of negotiate_alloc.

> In a sense, resize_alloc is really a more basic interface than
> negotiate_alloc.  Indeed the paper shows how negotiate_alloc can be
> built on top of resize_alloc with no compromises.  The main reason for
> negotiate_alloc to exist is to collect the multiple calls to
> resize_alloc required for the negotiation inside of the heap's mutex.

If implementing expand_to_eob within the current framework, what
guarantees would it offer?


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: phalpern@halpernwightsoftware.org (Pablo Halpern)
Date: Thu, 28 Jul 2005 04:43:21 GMT
Raw View
Howard Hinnant <hinnant@metrowerks.com> wrote:

>
>Your proposed swap swells memory usage by a factor of 2 during the swap.
>In a resource constrained environment this makes a failure more likely.

This is true in the case of unequal allocators, but there are mitigating
factors.  Please see my longer response in the thread, "Revised
Allocator Proposal". (I wish there were a way to link threads in net
news.  For Google Groups users, the thread can be found at
http://groups-beta.google.com/group/comp.std.c++/browse_thread/thread/574518e5c7a60feb/2bd4cc82997908ee)

Pablo Halpern                 phalpern@halpernwightsoftware.com
Author: The C++ Standard Library from Scratch
http://www.halpernwightsoftware.com/stdlib-scratch

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Thu, 21 Jul 2005 15:51:57 GMT
Raw View
On 2005-07-21 03:41, Andrei Alexandrescu wrote:
> David Abrahams wrote:
>> Andrei Alexandrescu writes:
>>>
>>>"Goner", n. - an object that provably has destruction as the only
>>>use after the current operation.
>>
>> That can be pretty darned hard to prove.  If the object's
>> constructor stores a reference to itself somewhere and there are
>> any objects (even temporaries) constructed afterwards in the same
>> scope, the jig is up, because any of them could try to use it.
>
> You got a point. In the case of "return a;" where a is a local, the
> proof is sort of trivial because any reference to a escapes (say in
> a global variable), it would be illegal anyway. So you don't need to
> worry about it since such code already has undefined behavior.

No, because "somewhere" can be another local object whose destructor
references 'a' and is being invoked after the evaluation of the return
statement proper. (See below.)

> Now, let me ask this. Would you agree to extend rvalue-ref
> bindability to "return a;", where "a" is the name of an rvalue
> reference? As in:
>
> A foo(A&& a)
> {
>    if (condition) return A(); // bindable to A(A&&)
>    return a; // bindable to A(A&&)
> }
>
> Or is there no need for thise because RVO can take care of it
> anyway? Or is there some danger in allowing it?

Consider:

   // with copy:

   A f(A&& a)
   {
      ON_BLOCK_EXIT(do_something_with, a);
      ...
      return a;   // invocation of copy constructor
                  // invocation of the scope guard's destructor,
                  // i.e. of do_something_with on original value of a
   }

   // with move:

   A f(A&& a)
   {
      ON_BLOCK_EXIT(do_something_with, a);
      ...
      return a;   // invocation of move constructor
                  // invocation of the scope guard's destructor,
                  // i.e. of do_something_with on default value of A
   }

This looks like it could constitute a danger.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Thu, 21 Jul 2005 15:50:10 GMT
Raw View
In article <IJy1qG.1GHs@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> Now I will try to slowly talk you into less and less restrictive cases.
> Imagine this:
>
> int f(A&&);
> int f(A&);
>
> int g()
> {
>    A a;
>    return f(a);
> }
>
> Would you agree that it would be nice to pass a to f(A&&)? We, of
> course, assume that f(A&&) is more efficient than f(A&) since it has the
> freedom of pilfering its argument however it wants, in addition to doing
> whatever f(A&0 does.
>
> So, if you allow "return a" to bind to an rvalue reference, why wouldn't
> you also allow "return f(a)"?

Unsure, sorry.  Still thinking it over...

> Also, the current proposal (if I understand it correctly) never allows
> rebinding automatically an incoming named rvalue reference back to an
> rvalue when returning it, which I believe is a problem. With the current
> proposal, even writing an identity function is gauche:
>
> template <class T>
> inline
> T&&
> identity(T&& x)
> {
>      return std::move(x);
> }
> // plus other overloads
>
> when at best we'd allow the obvious way of writing identity:
>
> template <class T>
> inline
> T&&
> identity(T&& x)
> {
>      return x;
> }
> // plus other overloads with IDENTICAL body

I don't believe you've yet internalized this rule:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Templat
e%20Argument%20Deduction%20with%20A&&

which admittedly can be very confusing on first contact.  But it also
makes perfect forwarding work very simply which makes it worth it.

Your latter identity compiles fine.  If it receives an lvalue A, it will
return an lvalue A (A&).  If it receives an rvalue A, it will return an
rvalue A (A&&).  The only tricky part is that if it receives a named
rvalue reference, that is treated as an lvalue, and thus an lvalue is
returned.  Else you get into double moving mode.  If you have a named
rvalue reference and you want to forward it as an rvalue reference, you
must explicitly specify the template parameter in identity.  And this is
why forward is defined as it is:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html#Forwar
ding

Without this feature you either get into double moving mode (named
rvalue references are considered rvalues),

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#More%20
on%20A&&

Or you get imperfect forwarding (if you drop the modified template
argument deduction rule):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm#s6

Your first identity will fail to compile if passed an lvalue:

Error   : illegal implicit conversion from 'int' to
'int &'
 (point of instantiation: 'main()')
  (instantiating: 'identity<int &>(int &)')
HelloWorld.cp line 9        return std::move(x);

    (corresponding point of instantiation for 'identity<int &>(int &)')
HelloWorld.cp line 15    i = identity(i);

Explanation:  When passed an lvalue int, the template parameter in
identity is deduced as int& (not int).  This makes the return type a:

int& && // pseudo code

which collapses to:

int&

So when you internally say move(x), you end up trying to bind an rvalue
int to an int& (the return type), and the compiler complains.

> In a function that has an unconditional last access, cool - a can be moved:
>
> int g()
> {
>    A a;
>    f(a); // calls f(A&); not an unconditional last access
>    if (condition) {
>       f(a); // calls f(A&); not an unconditional last access
>    }
>    f(a); // AHA! calls f(A&&); last unconditional access
>    return 42; // == the meaning of life
> }
>
> Would you believe that that's better?

Unsure.  Still thinking about it.  Part of my worry is how to describe
your intent with sufficient precision so that it can be standardized,
and vendors will come away with an unambiguous, uniform view of what is
required.  For example, say that we do say the above is ok.  How do we
distinguish that case from the following unsafe one:

int g()
{
   A a;
   B b;
   f(a); // calls f(A&); not an unconditional last access
   if (condition) {
      f(a,b); // calls f(A&); not an unconditional last access
   }
   f(a); // AHA! calls f(A&&); last unconditional access
   return 42; // == the meaning of life
}

The thing that makes this scary, and "return a" not, is that 'f' is a
general, do-anything, don't-know-what-it-does function.

In contrast, A(const A&), and presumably A(A&&) are functions with
pretty well defined semantics (at least A(const A&) is today).  The
semantics of this function are so well established, and even restricted,
that the compiler can optimize it away in some circumstances.  For
example clients can't portably depend upon a "counting copy constructor"
that keeps track of the number of copies (well they can write it, just
the result isn't required to be consistent across platforms).  There is
no such restriction on 'f'.

That is a very important part of the contract between programmers and
compilers.  That is what gives us a little freedom here.  And the
current proposal purposefully only extends this freedom for A(A&&) where
the current standard already explicitly provides it for A(const A&).
Yes, this is a conservative proposal - guilty as charged.  And even with
this conservatism, I'm clocking some examples running many times faster
- and no examples running slower.

> Now, let me ask this. Would you agree to extend rvalue-ref bindability
> to "return a;", where "a" is the name of an rvalue reference? As in:
>
> A foo(A&& a)
> {
>    if (condition) return A(); // bindable to A(A&&)
>    return a; // bindable to A(A&&)
> }
>
> Or is there no need for thise because RVO can take care of it anyway? Or
>   is there some danger in allowing it?

RVO isn't applicable here.  You can only RVO from auto-local storage
objects.

This would be a bad change because 'a' may be bound to an external
lvalue, and thus the move would be visible and perhaps unexpected.

Premise:  Explicit moves are ok.  You get what you ask for.  Implicit
moves are only ok if they can not be detected (except by measuring
performance).

Moving from an rvalue (with copy syntax) is ok because only one client
(the mover) can ever have a reference to the rvalue.  Changes the mover
makes to the rvalue aren't visible to the rest of the program.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com>
Date: 21 Jul 2005 19:30:01 GMT
Raw View
Ok everyone, sorry for being confused and confusing everybody in the
process.

David Abrahams wrote:
> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>You can only pilfer the parameter in f if you have overloaded f to take
>>both an A& and an A&&:
>>
>>f(A&);
>>f(A&&); // can pilfer because f(A&) exists
>
>
> Right.
>
>
>>Hmmmmm... that simplifies the case "I want to implement a function
>>that doesn't care whether the source is an rvalue or an lvalue". The
>>paper makes an argument that there are situation in which you do
>>want that, such as temporary streams, random_shuffle, etc.
>>
>>Such a setup, however, shots down some of the optimizations that I
>>suggest, because at the function level there is a loss of
>>information.  You can't reason in isolation about the nature of a
>>inside f(A&& a).  Actually it's very awkward: you can reason about
>>the nature of a inside f(A&& a), provided there exists an overload
>>f(A&).
>
>
> Yes, I agree in principle that you lose the ability to know that a
> reference is pilferable.  But those optimizations are already shot
> down for the most part, because the compiler usually can't deduce that
> pilferability is possible.
[snip print example]

True. Thanks for bringing the print example to my attention after I've
missed it the first time around :o).

Ok, so it seems like the notion of "last use" is too darn hard to point
down; because of that, it is as good as dead.

There is one case that remains, I believe, valid.

Under the following conditions:

1. A function f is overloaded: A f(A&) and A f(A&&);

and

2. The function f(A&& a) returns a;

Then that returned value can be bound to an rvalue reference. This is
because f(A&&) will only get rvalues. But this is quite an obscure
situation; I'm not sure if it's worth the effort.

>>This aspect of the proposal is optimization-unfriendly for the
>>benefit of a feature ("modifiable objects whether or not they
>>originate for temporaries") that I believe is obscure and can be
>>easily coded around with a forwarding overload.
>
>
> That's something we're trying to avoid.  Once you have multiple
> mutable reference arguments that can bind to rvalues as well as
> lvalues, the forwarding problem comes back.  Admittedly, this is not
> as important an instance the forwarding problem as some of the others,
> but getting rid of that problem was a major point of the proposal.

Fine.

>>Deciding that a is a goner in f(A&& a) iff there exists an overload
>>f(A&) is awkward.
>
>
> But the information that a is a goner isn't all that useful.  Just
> change foo above so that it takes greet as an argument by
> std::string&& and you'll see why.

True, but if we give up on the "last use" idea and apply the rule
defined abouve I think is safe.

Anyway... to summarize:

a) We've agreed that "throw expr;" should be treated identical to
"return expr;" as far as moveability goes.

b) The notion of "last use" is so complicated to define properly, it's
useless.

c) Named rvalues can't be considered disposable because they could
(absent an lvalue overload) bind to an lvalue.

d) There remains a potential for optimization of returned a named rvalue
reference from a function if there exists an overload of that function
taking a modifiable lvalue. But even if that would be correct (which
might not even be), it's too much effort for too little outcome.

Thanks everyone for elightening me and for putting up with my being
slow. I have personally reached the conclusion that the proposal
appropriately and satisfactorily solves what I believe is C++'s largest
source of inefficiency.

The second one is IMHO, by the way, memory reallocation, but that's the
subject of another proposal and of another exasperating thread
suggesting to improve things by 1%. :o)


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com>
Date: Thu, 21 Jul 2005 14:21:20 CST
Raw View
Niklas Matthies wrote:
> Consider:
>
>    // with copy:
>
>    A f(A&& a)
>    {
>       ON_BLOCK_EXIT(do_something_with, a);
>       ...
>       return a;   // invocation of copy constructor
>                   // invocation of the scope guard's destructor,
>                   // i.e. of do_something_with on original value of a
>    }
>
>    // with move:
>
>    A f(A&& a)
>    {
>       ON_BLOCK_EXIT(do_something_with, a);
>       ...
>       return a;   // invocation of move constructor
>                   // invocation of the scope guard's destructor,
>                   // i.e. of do_something_with on default value of A
>    }
>
> This looks like it could constitute a danger.

That is also an argument against binding a local lvalue to an rvalue
reference. No?

// with copy:

    A f()
    {
       A a;
       ON_BLOCK_EXIT(do_something_with, a);
       ...
       return a;   // invocation of copy constructor
                   // invocation of the scope guard's destructor,
                   // i.e. of do_something_with on original value of a
    }

    // with the current proposal:

    A f()
    {
       A a;
       ON_BLOCK_EXIT(do_something_with, a);
       ...
       return a;   // invocation of move constructor
                   // invocation of the scope guard's destructor,
                   // i.e. of do_something_with on default value of A
    }


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Thu, 21 Jul 2005 22:22:30 GMT
Raw View
On 2005-07-21 20:21, Andrei Alexandrescu (See Website For Email) wrote:
:
> That is also an argument against binding a local lvalue to an rvalue
> reference. No?
>
> // with copy:
>
>     A f()
>     {
>        A a;
>        ON_BLOCK_EXIT(do_something_with, a);
>        ...
>        return a;   // invocation of copy constructor
>                    // invocation of the scope guard's destructor,
>                    // i.e. of do_something_with on original value of a
>     }
>
>     // with the current proposal:
>
>     A f()
>     {
>        A a;
>        ON_BLOCK_EXIT(do_something_with, a);
>        ...
>        return a;   // invocation of move constructor
>                    // invocation of the scope guard's destructor,
>                    // i.e. of do_something_with on default value of A
>     }

I wasn't aware that the current proposal already allows this, but
now I've read up on it and see that it does. Yes, I see this as an
argument against it. I find it quite unsettling that the value of
a local variable may be pulled from under ones feet by such an
innocent return before the function has finished executing.
That looks like a recipe for subtle maintenance problems.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Thu, 21 Jul 2005 22:25:20 GMT
Raw View
In article <IJzMqt.1t9u@beaver.cs.washington.edu>,
 "Andrei Alexandrescu (See Website For Email)"
 <SeeWebsiteForEmail@moderncppdesign.com> wrote:

> Anyway... to summarize:
>
> a) We've agreed that "throw expr;" should be treated identical to
> "return expr;" as far as moveability goes.

<nod> I'm trying to figure out how to package this one.  I think as a
separate proposal would be appropriate.  There's already precedence for
that approach:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1784.htm

> d) There remains a potential for optimization of returned a named rvalue
> reference from a function if there exists an overload of that function
> taking a modifiable lvalue. But even if that would be correct (which
> might not even be), it's too much effort for too little outcome.

In the cases I've identified so far that look sort of like this, the
return type is actually A&&, and the A&& argument is returned:

A&& foo(A&& a) {return a;}

Practical application:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#string%
20Motivation

> The second one is IMHO, by the way, memory reallocation, but that's the
> subject of another proposal and of another exasperating thread
> suggesting to improve things by 1%. :o)

<raises hand>  Oh, oh, please see:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm

:-)

I figured it made sense to start that one at the C level.  So far though
it hasn't even been looked at in committee. :-(  Maybe Mont Tremblant
<fingers crossed>.

I currently have a vector<T, malloc_allocator<T> > that does both moving
based on N1377 (at el) and expand-in-place based on N1085 (C).  It blows
the doors off a built-in C-like array! :-)

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Fri, 22 Jul 2005 03:00:45 GMT
Raw View
Howard Hinnant wrote:
> In article <IJzMqt.1t9u@beaver.cs.washington.edu>,
>  "Andrei Alexandrescu (See Website For Email)"
>  <SeeWebsiteForEmail@moderncppdesign.com> wrote:
>>a) We've agreed that "throw expr;" should be treated identical to
>>"return expr;" as far as moveability goes.
>
>
> <nod> I'm trying to figure out how to package this one.  I think as a
> separate proposal would be appropriate.  There's already precedence for
> that approach:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1784.htm

Cool. This might reflect my naivete wrt standardization proposals, but I
was thinking that rewording the existing proposal to (1) group the
return and throw statements together, and (2) add a short section
covering exception propagation, would be the easiest.

>>d) There remains a potential for optimization of returned a named rvalue
>>reference from a function if there exists an overload of that function
>>taking a modifiable lvalue. But even if that would be correct (which
>>might not even be), it's too much effort for too little outcome.
>
>
> In the cases I've identified so far that look sort of like this, the
> return type is actually A&&, and the A&& argument is returned:
>
> A&& foo(A&& a) {return a;}
>
> Practical application:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#string%
> 20Motivation

Ah, great. Now I realize this is also scalable in a way because you must
modify the code to insert the rvalue reference anyway.

>>The second one is IMHO, by the way, memory reallocation, but that's the
>>subject of another proposal and of another exasperating thread
>>suggesting to improve things by 1%. :o)
>
>
> <raises hand>  Oh, oh, please see:
>
> http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm
>
> :-)
>
> I figured it made sense to start that one at the C level.  So far though
> it hasn't even been looked at in committee. :-(  Maybe Mont Tremblant
> <fingers crossed>.

Of course I knew about your proposal. It's great, and I believe of much
importance. I'm surprised it doesn't draw as much attention as it should
:o(.

> I currently have a vector<T, malloc_allocator<T> > that does both moving
> based on N1377 (at el) and expand-in-place based on N1085 (C).  It blows
> the doors off a built-in C-like array! :-)

By what margin does it "blow the door off" the legacy std::vector?
That's one tidbit of information that many would find compelling. With
remarkable regularity, whenever I bring the issue of in-place
reallocation, there are some people who say, ehm, most of the time
there's no more space at the end of the current block so you gotta do
the copying anyway. Some realistic numbers would be extremely helpful.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Fri, 22 Jul 2005 03:34:28 GMT
Raw View
Niklas Matthies wrote:
> I wasn't aware that the current proposal already allows this, but
> now I've read up on it and see that it does. Yes, I see this as an
> argument against it. I find it quite unsettling that the value of
> a local variable may be pulled from under ones feet by such an
> innocent return before the function has finished executing.
> That looks like a recipe for subtle maintenance problems.

IMHO sometimes it's worth to draw the line so that you favor improvement
on a lot of cases at the expense of complicating some rarely-encountered
cases. I guess law, insurance, and democracy would form good examples :o).

Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Fri, 22 Jul 2005 14:09:15 GMT
Raw View
In article <IK02ss.BKA@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> >>The second one is IMHO, by the way, memory reallocation, but that's the
> >>subject of another proposal and of another exasperating thread
> >>suggesting to improve things by 1%. :o)
> >
> >
> > <raises hand>  Oh, oh, please see:
> >
> > http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm
> >
> > :-)
> >
> > I figured it made sense to start that one at the C level.  So far though
> > it hasn't even been looked at in committee. :-(  Maybe Mont Tremblant
> > <fingers crossed>.
>
> Of course I knew about your proposal. It's great, and I believe of much
> importance. I'm surprised it doesn't draw as much attention as it should
> :o(.
>
> > I currently have a vector<T, malloc_allocator<T> > that does both moving
> > based on N1377 (at el) and expand-in-place based on N1085 (C).  It blows
> > the doors off a built-in C-like array! :-)
>
> By what margin does it "blow the door off" the legacy std::vector?
> That's one tidbit of information that many would find compelling. With
> remarkable regularity, whenever I bring the issue of in-place
> reallocation, there are some people who say, ehm, most of the time
> there's no more space at the end of the current block so you gotta do
> the copying anyway.

Somehow I doubt those people have written commercial grade
implementations of malloc.  But I could be mistaken.

> Some realistic numbers would be extremely helpful.

Timing this piece of code:

    const int n = 10000;
    Vec v;
    for (int i = 0; i < n; ++i)
        v.push_back(0);


When Vec is a vector<int> without expand-in-place:

0.00095674 seconds

When Vec is a vector<int> with expand-in-place:

0.000416943 seconds

The timings should only be trusted to 2 significant digits.  But rest
assured, I've done the work to secure those two digits.  We are not
looking at an anomaly here.

Of course these results are highly dependent on the underlying malloc,
and on usage patterns, YMMV.  This is simply what is possible.  In my
book, any time a factor of 2 speedup pops up, it is time to sit up, take
notice, investigate thoroughly, and prosecute aggressively.

Note that N1085 is not only about expand-in-place, it also has the
feature that extra capacity allocated by malloc (perhaps for alignment
purposes) can be automatically used.

Also, expand-in-place is not just a:

    Double my allocation or else

proposition.  Rather it goes more like:

Client: Can you double the size of my memory block without moving it?
malloc: No, but I can expand your 1000 byte block to 1500 bytes. Would
        you like me to do that?
Client: Sure, I can make use of the extra 500 bytes, thanks.

This provides extreme flexibility and efficiency to vector.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: seewebsiteforemail@moderncppdesign.com ("Andrei Alexandrescu (See Website for Email)")
Date: Fri, 22 Jul 2005 15:43:07 GMT
Raw View
Howard Hinnant wrote:
> Also, expand-in-place is not just a:
>
>     Double my allocation or else
>
> proposition.  Rather it goes more like:
>
> Client: Can you double the size of my memory block without moving it?
> malloc: No, but I can expand your 1000 byte block to 1500 bytes. Would
>         you like me to do that?
> Client: Sure, I can make use of the extra 500 bytes, thanks.
>
> This provides extreme flexibility and efficiency to vector.

Yah, we've talked about this protocol before. But I think it's more
like this (due to concurrency issues):

Client: Can you double the size of my memory block without moving it?
malloc: No, but I can expand your 1000 byte block to 1500 bytes.
**Here you go, I've done it for ya, otherwise some other dude could
allocate in another thread that memory and render me unable to respect
my offer.**
Client: Sure, I can make use of the extra 500 bytes, thanks.
malloc: Say, what do you do after hours? Maybe we can drink some drain
current together.

No?


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Fri, 22 Jul 2005 16:15:01 GMT
Raw View
usenet-nospam@nmhq.net (Niklas Matthies) writes:

> On 2005-07-21 20:21, Andrei Alexandrescu (See Website For Email) wrote:
> :
>> That is also an argument against binding a local lvalue to an rvalue
>> reference. No?
>>
>> // with copy:
>>
>>     A f()
>>     {
>>        A a;
>>        ON_BLOCK_EXIT(do_something_with, a);
>>        ...
>>        return a;   // invocation of copy constructor
>>                    // invocation of the scope guard's destructor,
>>                    // i.e. of do_something_with on original value of a
>>     }
>>
>>     // with the current proposal:
>>
>>     A f()
>>     {
>>        A a;
>>        ON_BLOCK_EXIT(do_something_with, a);
>>        ...
>>        return a;   // invocation of move constructor
>>                    // invocation of the scope guard's destructor,
>>                    // i.e. of do_something_with on default value of A
>>     }
>
> I wasn't aware that the current proposal already allows this, but
> now I've read up on it and see that it does. Yes, I see this as an
> argument against it. I find it quite unsettling that the value of
> a local variable may be pulled from under ones feet by such an
> innocent return before the function has finished executing.
> That looks like a recipe for subtle maintenance problems.

Actually, if the proposal currently allows that behavior we think it's
underspecified.  The above code should be required to be "as if":


   A f()
   {
      A a;
      {
         ON_BLOCK_EXIT(do_something_with, a);
         ...
                  // invocation of the scope guard's destructor,
                  // i.e. of do_something_with on original value of a
      }
      return a;   // invocation of move constructor
   }


Thanks for bringing it to our attention; we're going to make sure the
spec says what we mean.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Fri, 22 Jul 2005 16:21:50 GMT
Raw View
On 2005-07-22 03:34, "Andrei Alexandrescu (See Website For Email)" wrote:
> Niklas Matthies wrote:
>> I wasn't aware that the current proposal already allows this, but
>> now I've read up on it and see that it does. Yes, I see this as an
>> argument against it. I find it quite unsettling that the value of
>> a local variable may be pulled from under ones feet by such an
>> innocent return before the function has finished executing.
>> That looks like a recipe for subtle maintenance problems.
>
> IMHO sometimes it's worth to draw the line so that you favor
> improvement on a lot of cases at the expense of complicating some
> rarely-encountered cases.

One problem is that it means that adding a move constructor to some
class may break existing code.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Niklas Matthies <usenet-nospam@nmhq.net>
Date: Fri, 22 Jul 2005 12:37:26 CST
Raw View
On 2005-07-22 16:15, David Abrahams wrote:
> usenet-nospam@nmhq.net (Niklas Matthies) writes:
:
>>>     A f()
>>>     {
>>>        A a;
>>>        ON_BLOCK_EXIT(do_something_with, a);
>>>        ...
>>>        return a;   // invocation of move constructor
>>>                    // invocation of the scope guard's destructor,
>>>                    // i.e. of do_something_with on default value of A
>>>     }
>>
>> I wasn't aware that the current proposal already allows this, but
>> now I've read up on it and see that it does. Yes, I see this as an
>> argument against it. I find it quite unsettling that the value of
>> a local variable may be pulled from under ones feet by such an
>> innocent return before the function has finished executing.
>> That looks like a recipe for subtle maintenance problems.
>
> Actually, if the proposal currently allows that behavior we think it's
> underspecified.  The above code should be required to be "as if":
>
>
>    A f()
>    {
>       A a;
>       {
>          ON_BLOCK_EXIT(do_something_with, a);
>          ...
>                   // invocation of the scope guard's destructor,
>                   // i.e. of do_something_with on original value of a
>       }
>       return a;   // invocation of move constructor
>    }

This is different from the order with copy construction, which
could be a little confusing. But I guess it's still quite an
acceptable compromise.

Which makes me aware of the fact that having 'do_something_with'
modify 'a' is already a bad idea in the current language because
whether it affects the returned value depends on whether RVO kicks
in or not. So it's not really worse if it now additionally depends
on whether move construction is used instead of copy construction.

Oh well. Return values seem such a simple concept, but sadly it's
really a mess.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Niklas Matthies <usenet-nospam@nmhq.net>
Date: 22 Jul 2005 19:30:01 GMT
Raw View
On 2005-07-22 18:37, Niklas Matthies wrote:
> On 2005-07-22 16:15, David Abrahams wrote:
>> usenet-nospam@nmhq.net (Niklas Matthies) writes:
:
>> Actually, if the proposal currently allows that behavior we think it's
>> underspecified.  The above code should be required to be "as if":
>>
>>
>>    A f()
>>    {
>>       A a;
>>       {
>>          ON_BLOCK_EXIT(do_something_with, a);
>>          ...
>>                   // invocation of the scope guard's destructor,
>>                   // i.e. of do_something_with on original value of a
>>       }
>>       return a;   // invocation of move constructor
>>    }
>
> This is different from the order with copy construction, which
> could be a little confusing. But I guess it's still quite an
> acceptable compromise.
>
> Which makes me aware of the fact that having 'do_something_with'
> modify 'a' is already a bad idea in the current language because
> whether it affects the returned value depends on whether RVO kicks
> in or not. So it's not really worse if it now additionally depends
> on whether move construction is used instead of copy construction.

An additional thought: It might be worth for the next standard to
require that if a copy or move constructor is invoked for a return
value, than it has to be invoked immediately before the corresponding
source object is destroyed (i.e. not earlier). This would make the
overall behavior more deterministic.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Fri, 22 Jul 2005 19:27:19 GMT
Raw View
usenet-nospam@nmhq.net (Niklas Matthies) writes:

> On 2005-07-22 03:34, "Andrei Alexandrescu (See Website For Email)" wrote:
>> Niklas Matthies wrote:
>>> I wasn't aware that the current proposal already allows this, but
>>> now I've read up on it and see that it does. Yes, I see this as an
>>> argument against it. I find it quite unsettling that the value of
>>> a local variable may be pulled from under ones feet by such an
>>> innocent return before the function has finished executing.
>>> That looks like a recipe for subtle maintenance problems.
>>
>> IMHO sometimes it's worth to draw the line so that you favor
>> improvement on a lot of cases at the expense of complicating some
>> rarely-encountered cases.
>
> One problem is that it means that adding a move constructor to some
> class may break existing code.

It shouldn't.  See my other posting
(http://groups-beta.google.com/group/comp.std.c++/msg/a5578fc334f86b1e?hl=en&)

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Tue, 19 Jul 2005 16:10:59 GMT
Raw View
In article <IJutHJ.13nn@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> I don't have an entirely formed idea of a precise wording, but I think
> the basics are:

I look forward to your precisely worded suggestions for improving this
part of the C++ standard.

> What do you think?

In general I have no objection to your "last reference" approach.
Indeed your suggestion with throwing an auto-local is I think right on
target.  But I think I may have serious objections to your definition of
"last reference" in other contexts, especially with respect to function
arguments:

void f(A&& a)
{
   ...
   g(a);  // last reference?
}

The current design is strongly influenced by lengthy discussions on
comp.lang.c++.moderated regarding this very detail that took place over
five years ago (and in which you played a significant role).  Since then
the problem has been studied extensively, prototyped, debated repeatedly
in committee and in sub-working groups, detailed proposed wording has
been published, and there is now a complete implementation on which
tests/experiments are being performed daily.

Standardizing a language change with such a wide ripple effect is so
much more than saying "Hey, we should do this!"  This is a decade long
effort involving continual commitment and careful scheduling.  We are
now in the latter stages of this project aimed at C++0X.  And at this
stage I am naturally very cautious of major changes and will scrutinize
them extremely closely.

And of course I think we need to keep an eye on implementability.  Imho
anything that requires interprocedural analysis (or whole program
analysis) just to conform is not going to meet the implementability test
(even if it is implementable).

>    g(A().b_); // calls g(B&&);

I believe we already have this.  A().b_ is an rvalue under C++98 rules,
at least according to CodeWarrior and EDG front ends (I believe 5.2.5p4
is the relevant part of the standard, but I could be mistaken).

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Tue, 19 Jul 2005 20:24:23 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> This is a very sensitive point because exceptions occur when already
> the system might be resource-cramped, and as such the last thing we
> need is to unnecessarily duplicate resources. That is, by the way,
> why the C++ Coding Standards book suggests using mutable members and
> move semantics in exception classes if copying fails.
>
> But wait, there's less. When propagating the exception, the current
> standard says that the copy constructor will be called an
> implementation-defined number of times. So if I just want to do:
>
> void bar()
> {
>   try
>   {
>     foo();
>   }
>   catch (MyException& e)
>   {
>     cerr << "And here we go with some more copying...\n";
>     throw; // could copy some more!
>   }
> }
>
> Because the proposal doesn't state exactly under which condition a
> value can be bound to an rvalue reference, it's hard to clarify
> whether propagation during throw will be made via rvalue
> references.

What does "made via rvalue references" mean?

> Here's a quote:
>

<snip descriptive text>

This is all "non-normative text," though.  Did you look at the
proposed standard wording?

> What I see here is a lot of pointing at the trees and a failure to
> show the forest. When this is applied to my example with exceptions,
> "throw x;" is guaranteed to issue a full-blown copy

Unless the compiler can prove you won't observe it.

> and the subsequent propagation is tricky because it's unclear
> whether the exception being propagated is an rvalue.

Tricky how?


> Let me clarify point 3. Consider:
>
> struct B
> {
>   ...
> };
>
> struct A
> {
>   B b_;
>   ...
> };
>
> void g(const B&);
> void g(B&&);
>
> void f()
> {
>   A a;
>   g(a.b_); // calls g(const B&);
>   g(a.b_); // calls g(B&&);
>   g(A().b_); // calls g(B&&);
> }

I just want to be clear that this case is totally different from the
ones you were describing earlier, where a was passed in by && from the
outside.

> So basically the rule number 3 nicely propagates the rules to members.
> That way the safe optimization opportunities are extended, and there are
> fewer casts needed.
>
> What do you think?

I'm not sure if it's fundamentally workable, but if it is, I am still
a little worried.  An object is not just a bag of members: they have
relationships that make up the object invariant, and some destructors
are not just a bag of member destructors -- they may depend on those
relationships having been preserved.  If a function can cause
arbitrary combinations of members to be moved from implicitly,
depending on the access pattern, you could be in for some very
surprising effects.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Tue, 19 Jul 2005 20:23:51 GMT
Raw View
FWIW, I agree with all of Howard's foregoing points about why the
compiler shouldn't move implicitly except in the cases already
prescribed in the paper; I just didn't have time to write out my
reasoning.  But Howard said it all.

hinnant@metrowerks.com (Howard Hinnant) writes:

>> Such a rule would also enable better forwarding. Consider the way the
>> unwary might be thinking of implementing forwarding:
> <snip>
>> The current solution is to
>> prescribe a cast that manually realizes the notion of last access:
>>
>> void forwarder(const A& a) {
>>      g(a);
>> }
>>
>> void forwarder(A&& a) {
>>      g(static_cast<A&&>(a));
>> }
>
> I'd prefer the more descriptive:
>
> void forwarder(A&& a) {
>      g(std::forward<A>(a));
> }
>
> Personally I think the explicitness of this earns bonus points (e.g. you
> can search for "forward"), especially in the light that A&& doesn't
> automatically mean forward.  For example:

And most especially because static_cast is unsafe while std::forward
is not.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 19 Jul 2005 20:30:58 GMT
Raw View
David Abrahams wrote:
> Well, you could try to write a proposal around the idea that rvalue
> references mean fundamentally that there is a reference that won't be
> accessed beyond its current scope, and I invite you to be my guest!
> You'd have to start from the ground up and rethink everything, because
> it's a major departure from the foundation of our proposal.

I wasn't thinking it's a major departure. Sigh. My hope is to convince
the authors of the proposal that they can improve it, and to work with
them towards a better, more elegant and comprehensive one, not to be
challenged to rewrite it alone from scratch. Why not work together?
Let's stay on the positive side of things instead of rejecting anything
that is not in the current proposal.

I believe there's a ton of merit in the current proposal. It clarified
to me many things that I hadn't figured out for years. But there are
ways - some obvious, such as specifying move copying with "throw" - in
which it can be improved.

Again, I believe the improvements that can be brought are:

1. Eliminate copying during exception throwing and propagation

2. Simplify optimized user code by eliminating the need to insert a cast
(or std::move) in certain cases

3. Extend move semantics to member variables, thus simplifying writing
of move constructors and allowing more optimizations in some other cases
(such as when returning the member of a value).

All within a unified, simple view; instead of adding more particular
cases, we should express the notion of an rvalue reference in a way that
  makes these cases just natural effects.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com>
Date: 19 Jul 2005 20:40:01 GMT
Raw View
Howard Hinnant wrote:
> I look forward to your precisely worded suggestions for improving this
> part of the C++ standard.

Ok, I will do my best.

> In general I have no objection to your "last reference" approach.
> Indeed your suggestion with throwing an auto-local is I think right on
> target.  But I think I may have serious objections to your definition of
> "last reference" in other contexts, especially with respect to function
> arguments:
>
> void f(A&& a)
> {
>    ...
>    g(a);  // last reference?
> }
>
> The current design is strongly influenced by lengthy discussions on
> comp.lang.c++.moderated regarding this very detail that took place over
> five years ago (and in which you played a significant role).  Since then
> the problem has been studied extensively, prototyped, debated repeatedly
> in committee and in sub-working groups, detailed proposed wording has
> been published, and there is now a complete implementation on which
> tests/experiments are being performed daily.

I understand the source of your objection. It would be for the first
time an overload is selected depending on flow. I believe it's a good
thing to do, but it might be not palatable to some.

But since "a" in your example above is a disposable object, wouldn't it
come natural that its last reference binds to an rvalue? That would save
much code an extra cast.

At least if there's no overloading? If only g(A&&) is there...?

> Standardizing a language change with such a wide ripple effect is so
> much more than saying "Hey, we should do this!"  This is a decade long
> effort involving continual commitment and careful scheduling.  We are
> now in the latter stages of this project aimed at C++0X.  And at this
> stage I am naturally very cautious of major changes and will scrutinize
> them extremely closely.

I understand.

> And of course I think we need to keep an eye on implementability.  Imho
> anything that requires interprocedural analysis (or whole program
> analysis) just to conform is not going to meet the implementability test
> (even if it is implementable).

This is a misunderstanding. In my wildest dreams, I'm talking about a
standard INTRAprocedural analysis that will figure out the last access
of a ***local*** value through any alias. For example:

void foo(A&& a)
{
     A& alias = a;
     g(a);
     g(alias); // last access
     g(A());
}

>>   g(A().b_); // calls g(B&&);
>
>
> I believe we already have this.  A().b_ is an rvalue under C++98 rules,
> at least according to CodeWarrior and EDG front ends (I believe 5.2.5p4
> is the relevant part of the standard, but I could be mistaken).

That's good. My proposal would extend that to alias.b_, where alias is
the last reference to a lvalue in a scope.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Tue, 19 Jul 2005 20:38:07 GMT
Raw View
In article <IJv8D2.1qGp@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> Again, what I'd want is us to work together
> towards a perhaps better proposal, not me defending a view that's not
> entirely formed yet.

Understood, and input welcomed.

> Howard Hinnant wrote:
> > Here's a complete program that will either assert or not, depending upon
> > the application of the N1377 rules, or your proposed modification to
> > those rules.
> >
> > #include <utility>
> > #include <cassert>
> >
> > struct A
> > {
> >     A() : data_(1) {}
> >     A(A&& a) : data_(a.data_) {a.data_ = 0;}
> >     int data_;
> > };
> >
> > void g(const A&) {}
> > void g(A&& a) {a.data_ = 0;}
> >
> > void h(const A&) {}
> > void h(A&& a) {a.data_ = 0;}
> >
> > void f(A&& a)
> > {
> >       g(a);  // calls g(const A&)
> >       h(a);  // calls h(const A&)
> >       if (true)
> >       {
> >           h(a); // calls h(A&&) (by Alexandrescu rules)
> >       }
> > }
> >
> > int main()
> > {
> >     A a;
> >     f(a);
> >     assert(a.data_ == 1);
> > }
>
> That's a misunderstanding. To prove that the assert is happening, you
> must access the value. By my rules the lvalue, if you do access it,
> won't be bound to a rvalue reference. It's like in quantum mechanics: to
> measure it, you gotta touch it. If you touch it, you disturb it and you
> can't measure it.

The assert is touching 'a' in main.  And that is not an
rvalue_reference.  Without further documentation, the interface to
f(A&&) is simply that it will take an lvalue or an rvalue (non const,
non volatile) A.  This interface does not imply that you can not inspect
'a' after the call.  Of course neither does the interface promise not to
change 'a', and if it does, it should document how.  But the
modification is going to be visible.

Personally I'm the kind of guy would stick an explicit move on that last
call to h(a) if that's what I wanted (no matter what the rule).  But
then I also religiously put the virtual qualifier on derived function
signatures even when they are already implicitly virtual (C++ CS #38 ;-)
).

Otoh, if there is no visibility, I'm all for eliminating those "moves"
where one would naturally want them 100% of the time anyway, and even
more importantly, if included would inhibit an even more important
optimization:

A foo()
{
    A a;
    return std::move(a);  // bad! must call A(cv A&&) if it exists, else
A(cv A&)
}

Here the explicit move, even though desired, is bad because it inhibits
the compiler from eliding the move/copy (and elision is always
preferred).  Elision can only take place if an A is returned, not a
reference to one.

A foo()
{
    A a;
    return a;
}

Much better, return is either elided, moved or copied (in that order).

Your throw example is similar:

void foo()
{
    A a;
    throw a;  // copy can be elided
}

void bar()
{
     try
     {
         foo();
     }
     catch (A& a)
     {
          a.do_something();
     }
}

I believe 15.1p5 gives permission to construct 'a' in a special place
such that it binds directly to the handler in bar(), eliminating the
temporary A.

void foo()
{
    A a;
    throw std::move(a);  // not sure if move/copy can be elided now,
probably not
                         // since compiler will probably not bother
discovering
                         // that return of move is reference to 'a', (as
opposed
                         // to a reference to something else entirely).
}

Note that in both of these cases we do get a lucky break for the common
case that the returned/thrown object is not named:

void foo()
{
    throw A();  // automatically moved or elided
}

The CW compiler elides the move/copy of A here, but not here:

void foo()
{
    throw std::move(A());  // moved, not elided
}

So in summary, I believe that an explicit move on either the return or
throw of an auto-local variable is not only redundant, but actually
harmful because it inhibits move/copy elision.

But in this case:

void foo()
{
    A a;
    g(a);
    h(a);  // a is rvalue?
}

the motivation is not nearly as strong.  The explicit move can be added
with no harm.  And putting in an explicit move (if desired) is likely to
be considered good programming style even if there is an implicit move.

And when you start moving into this case:

void foo()
{
    A a;
    g(a);
    if (condition)
       h(a);
}

things get fuzzier.  An implicit move on h(a) is probably not
detectable.  But if I wanted 'a' moving in here I would end up coding
the following anyway for higher performance:

void foo()
{
    A a;
    if (condition)
    {
       g(a);
       h(std::move(a));
    }
    else
       g(std::move(a));
}

If we had the last reference rule, one might argue you could simplify
that to:

void foo()
{
    A a;
    if (condition)
    {
       g(a);
       h(a);
    }
    else
       g(a);
}

But that is so weird looking, that a maintenance programmer is likely to
further simplify that back to:

void foo()
{
    A a;
    g(a);
    if (condition)
       h(a);
}

!!! :-)

C++ Coding Standard #102:  Always depend on implicit moves on return and
throw statements.  And never depend upon implicit moves (from lvalues)
in any other statement.  ;-)

So I'm under-motivated except for the (existing) return statement, and
your proposed throw statement.

One cool thing about putting this into the throw statement:  It makes
practical movable but non-copyable exception objects.  That to me seems
very motivating - worth fighting for.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 19 Jul 2005 20:49:09 GMT
Raw View
David Abrahams wrote:
> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>Because the proposal doesn't state exactly under which condition a
>>value can be bound to an rvalue reference, it's hard to clarify
>>whether propagation during throw will be made via rvalue
>>references.
>
>
> What does "made via rvalue references" mean?

I meant that it's unclear that the move constructor (if the exception
class defines it) will be used when copying the exception during a throw
operation.

>>void g(const B&);
>>void g(B&&);
>>
>>void f()
>>{
>>  A a;
>>  g(a.b_); // calls g(const B&);
>>  g(a.b_); // calls g(B&&);
>>  g(A().b_); // calls g(B&&);
>>}
>
>
> I just want to be clear that this case is totally different from the
> ones you were describing earlier, where a was passed in by && from the
> outside.

That is correct, and the idea occured to me after I'd written the first
post.

> I'm not sure if it's fundamentally workable, but if it is, I am still
> a little worried.  An object is not just a bag of members: they have
> relationships that make up the object invariant, and some destructors
> are not just a bag of member destructors -- they may depend on those
> relationships having been preserved.  If a function can cause
> arbitrary combinations of members to be moved from implicitly,
> depending on the access pattern, you could be in for some very
> surprising effects.

That's a good point. Sigh. It's never easy is it :o). I'm thinking, if
the member is public, there's no problem since code could modify it
anyway. But if we're talking about a private or friend function... not
as clear, sigh.

So it looks like that part of my suggestion is flawed.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 03:41:42 GMT
Raw View
hinnant@metrowerks.com (Howard Hinnant) writes:

> In article <IJv8D2.1qGp@beaver.cs.washington.edu>,
>  SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
>  Website For Email)") wrote:
>
>> Again, what I'd want is us to work together
>> towards a perhaps better proposal, not me defending a view that's not
>> entirely formed yet.
>
> Understood, and input welcomed.

Agreed.  That said, however, this is IMO a very bad time
administratively to mess with what we have (a year ago would have been
much better; 6 or even 4 months ago would have been workable).  The
proposal has gone to the CWG for review and seems to be (fingers
crossed) headed for WP status.  I don't want to see it derailed just
because there are optimization opportunities that we didn't take
advantage of.  It might be different if what you're talking about was
a crucial optimization that required fundamental changes in what we're
proposing -- because then the right thing to to would be to start over
-- but AFAICT, what you're proposing, when it gets worked out in
detail, could be added with minimal disturbance to what's in the
existing proposal.  I am highly inclined to get a very useful proposal
accepted now, erring on the conservative side, and then look at how to
make it more permissive.  Nothing will kill C++ move semantics faster
than a perception in the committee that the proposal is overreaching.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 03:42:20 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> David Abrahams wrote:
>> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>>Because the proposal doesn't state exactly under which condition a
>>>value can be bound to an rvalue reference, it's hard to clarify
>>>whether propagation during throw will be made via rvalue
>>> references.
>> What does "made via rvalue references" mean?
>
> I meant that it's unclear that the move constructor (if the
> exception class defines it) will be used when copying the exception
> during a throw operation.

Oh.  Yeah, that seems like low-hanging fruit.

>>>void g(const B&);
>>>void g(B&&);
>>>
>>>void f()
>>>{
>>>  A a;
>>>  g(a.b_); // calls g(const B&);
>>>  g(a.b_); // calls g(B&&);
>>>  g(A().b_); // calls g(B&&);
>>>}
>> I just want to be clear that this case is totally different from the
>> ones you were describing earlier, where a was passed in by && from the
>> outside.
>
> That is correct, and the idea occured to me after I'd written the
> first post.

Okay.  I feel it's important to highlight that fact because the main
disagreement you and Howard (and I) are having is concerned with what
happens when an rvalue reference binds to an lvalue, just because
there's no overload accepting an lvalue reference.  That effect goes
away in this example.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 03:43:56 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> David Abrahams wrote:
>> Well, you could try to write a proposal around the idea that rvalue
>> references mean fundamentally that there is a reference that won't be
>> accessed beyond its current scope, and I invite you to be my guest!
>> You'd have to start from the ground up and rethink everything, because
>> it's a major departure from the foundation of our proposal.
>
> I wasn't thinking it's a major departure.

I understand that.  As I wrote:

  An rvalue reference means a reference that *can* be bound to an
  rvalue (which is an object that won't be accessed beyond its current
  scope).

In case it wasn't totally obvious, I mean to say that an rvalue
reference can also be bound to an lvalue.  Much of what you've
proposed up to this point in the thread seems to be predicated on the
idea that when you have an rvalue reference to an object, it means
nobody else is going to touch it.  That, my friend, is a major
departure.

> Sigh. My hope is to convince the authors of the proposal that they
> can improve it, and to work with them towards a better, more elegant
> and comprehensive one, not to be challenged to rewrite it alone from
> scratch. Why not work together?  Let's stay on the positive side of
> things instead of rejecting anything that is not in the current
> proposal.

I'm not rejecting anything.

I think you should try to understand my reaction in a historical
context.  I am more than a little bit protective of what we have
achieved in that proposal.  We've been working on it since early 2002,
and have gone through probably hundreds of hours of hairy discussions
considering interactions between rvalue references and existing
language features and looking over myriad use cases.  The way the
current proposal works out balances all these different forces in a
delicate web, and it's not something to disturb lightly.

We've weathered a request from the committee to form a subcommittee
and reconsider both the syntax -- to which a quite few people had a
bad reaction -- and the fundamental approach, because some thought
that this was all a simple matter of allowing constructors that take
arguments by value and expanding the copy elision rules.  We worked
with that subcommittee and it turned out, after much earnest
searching, that none of the other ideas worked well.  So there's a
history of people taking a brief look and deciding that it could be
done better, without really understanding the consequences of their
ideas, and ultimately impeding the progress toward getting move
semantics and forwarding in C++.

> I believe there's a ton of merit in the current proposal. It
> clarified to me many things that I hadn't figured out for years. But
> there are ways - some obvious, such as specifying move copying with
> "throw" - in which it can be improved.

That one looks pretty obvious to me, but also pretty orthogonal, so it
could be a separate proposal.  Some of the other ones that you seem to
think are obvious are a lot less so from my perspective.

> Again, I believe the improvements that can be brought are:
>
> 1. Eliminate copying during exception throwing and propagation

Low-hanging fruit.

> 2. Simplify optimized user code by eliminating the need to insert a cast
> (or std::move) in certain cases

That one is a concern for me.

> 3. Extend move semantics to member variables, thus simplifying writing
> of move constructors and allowing more optimizations in some other cases
> (such as when returning the member of a value).

That one is also a concern for me.

> All within a unified, simple view; instead of adding more particular
> cases, we should express the notion of an rvalue reference in a way
> that makes these cases just natural effects.

That sounds very familiar to me.  The subcommittee was formed to look
at this in part because some people thought we could express
constructors and copy elision in a way that move semantics just became
a natural effect.  I'm not opposed to the idea if you can do it, but
I'm a lot less sanguine about it than you appear to be, and I don't
have the energy to start from the ground up.  What I can do, for now,
is try to get you to take some of the concerns that Howard and I have
expressed more seriously.

When you ask why not work together, I guess my answer is that I am
afraid to get into a project with someone who doesn't appreciate how
hard the problem is.  I could, of course, be totally wrong about your
point-of-view (or for that matter how hard the problem is), but so far
that's my impression.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com>
Date: Tue, 19 Jul 2005 22:45:58 CST
Raw View
Howard Hinnant wrote:
>>>int main()
>>>{
>>>    A a;
>>>    f(a);
>>>    assert(a.data_ == 1);
>>>}
>>
>>That's a misunderstanding. To prove that the assert is happening, you
>>must access the value. By my rules the lvalue, if you do access it,
>>won't be bound to a rvalue reference. It's like in quantum mechanics: to
>>measure it, you gotta touch it. If you touch it, you disturb it and you
>>can't measure it.
>
>
> The assert is touching 'a' in main.  And that is not an
> rvalue_reference.  Without further documentation, the interface to
> f(A&&) is simply that it will take an lvalue or an rvalue (non const,
> non volatile) A.  This interface does not imply that you can not inspect
> 'a' after the call.  Of course neither does the interface promise not to
> change 'a', and if it does, it should document how.  But the
> modification is going to be visible.

True. But that's still a misunderstanding: my suggestion doesn't mandate
binding a to A&&.

I apologize for the continued misunderstanding; it's my fault in
clarifying (to myself first) what I mean.

My idea is this: The last statically-known access to a value /within its
lifetime/ is bindable to an rvalue reference.

In other words: if after f(a) the only thing you'll ever do will be to
destroy a, you can bind a to an rvalue reference because it's a goner
anyway - much like a returned value.

Under my suggested enhancement, your example above won't wrongly bind a
to an A&&. It will fail to compile. (It could be made to work if we
automatically create a copy when passing a.)

> Otoh, if there is no visibility, I'm all for eliminating those "moves"
> where one would naturally want them 100% of the time anyway

Awesome. That's the spirit. Exactly what I want too. Do you agree that a
"goner" can be bound to an rvalue reference - and not only in the
particular case of return or throw? That's

"Goner", n. - an object that will be destroyed right after the current
operation.

> , and even
> more importantly, if included would inhibit an even more important
> optimization:
>
> A foo()
> {
>     A a;
>     return std::move(a);  // bad! must call A(cv A&&) if it exists, else
> A(cv A&)
> }

Aha! Can you think of an example when introducing std::move would harm
when passing something to a function?

void foo()
{
     A a;
     goo(std::move(a)); // worse than goo(a);?
}

> So in summary, I believe that an explicit move on either the return or
> throw of an auto-local variable is not only redundant, but actually
> harmful because it inhibits move/copy elision.

I see.

> But in this case:
>
> void foo()
> {
>     A a;
>     g(a);
>     h(a);  // a is rvalue?
> }
>
> the motivation is not nearly as strong.  The explicit move can be added
> with no harm.

I understand your point. However, if the optimization can be done
without harm, why not allow it?

>  And putting in an explicit move (if desired) is likely to
> be considered good programming style even if there is an implicit move.

I disagree. This kind of stuff is best left to the compiler. To the code
writer it doesn't make any difference. The variable is a goner, so it
can be bound to an rvalue reference anyway. The compiler should be doing
that. Don't leave that kind of unscalable burden up to all programmers.
The calls to std::move would be pervasive, and as such they would be at
best automated.

> And when you start moving into this case:
>
> void foo()
> {
>     A a;
>     g(a);
>     if (condition)
>        h(a);
> }
>
> things get fuzzier.  An implicit move on h(a) is probably not
> detectable.  But if I wanted 'a' moving in here I would end up coding
> the following anyway for higher performance:
>
> void foo()
> {
>     A a;
>     if (condition)
>     {
>        g(a);
>        h(std::move(a));
>     }
>     else
>        g(std::move(a));
> }
>
> If we had the last reference rule, one might argue you could simplify
> that to:
>
> void foo()
> {
>     A a;
>     if (condition)
>     {
>        g(a);
>        h(a);
>     }
>     else
>        g(a);
> }
>
> But that is so weird looking, that a maintenance programmer is likely to
> further simplify that back to:
>
> void foo()
> {
>     A a;
>     g(a);
>     if (condition)
>        h(a);
> }
>
> !!! :-)

I'm not sure I understand this point. With the implicit move, the
optimal code is generated in all cases.

> C++ Coding Standard #102:  Always depend on implicit moves on return and
> throw statements.  And never depend upon implicit moves (from lvalues)
> in any other statement.  ;-)

Why???

> One cool thing about putting this into the throw statement:  It makes
> practical movable but non-copyable exception objects.  That to me seems
> very motivating - worth fighting for.

Yes, because the throw process can be rendered guaranteed nothrow.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 15:07:42 GMT
Raw View
"Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes:

> My idea is this: The last statically-known access to a value /within its
> lifetime/ is bindable to an rvalue reference.

But none other, apparently?  That's a major difference from our
proposal.

> In other words: if after f(a) the only thing you'll ever do will be to
> destroy a, you can bind a to an rvalue reference because it's a goner
> anyway - much like a returned value.
>
> Under my suggested enhancement, your example above won't wrongly bind a
> to an A&&. It will fail to compile. (It could be made to work if we
> automatically create a copy when passing a.)

Then it's not really an enhancement; it's a trade-off.  In our
proposal, that compiles.  That's how we handle the stream output
function that accepts ostream rvalues and lvalues.  Streams shouldn't
be copyable, so you can't make it work by copying the stream, and you
need to be able to pass the same stream to the function multiple times
in the same scope.

>> Otoh, if there is no visibility, I'm all for eliminating those
>> "moves" where one would naturally want them 100% of the time anyway
>
> Awesome. That's the spirit. Exactly what I want too. Do you agree
> that a "goner" can be bound to an rvalue reference - and not only in
> the particular case of return or throw?

That _can_ already happen in our proposal.  However, it's not
guaranteed to happen, and rvalue references can also bind to
non-goners.  What you want, IIUC, is:

  An rvalue reference binds to a named object (in preference to lvalue
  references) if and only if the object is a goner.

It's an attractively simple idea, but I'm not sure that it actually
works.  To be sure, one would have to go through
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html and
see how well each case was handled.  In particular, you'd have to keep
the deduction and reference-collapsing rules in place for template
<class T> f( ... T&&...) arguments.  And you would have to decide that
the case where you want one non-templated overload to handle non-const
lvalues and rvalues is not so important, or find another way to handle
it.

> That's
>
> "Goner", n. - an object that will be destroyed right after the current
> operation.
>
>> , and even more importantly, if included would inhibit an even more
>> important optimization:
>> A foo()
>> {
>>     A a;
>>     return std::move(a);  // bad! must call A(cv A&&) if it exists,
>> else A(cv A&)
>> }
>
> Aha! Can you think of an example when introducing std::move would harm
> when passing something to a function?
>
> void foo()
> {
>     A a;
>     goo(std::move(a)); // worse than goo(a);?
> }

In that particular case?  No.  Generally speaking?  Of course!  Here's
a trivial variation, for example:

  void foo()
  {
     A a;
     goo(std::move(a), std::move(a));
  }

That's like doing

     goo(++i, ++i);

>>  And putting in an explicit move (if desired) is likely to be
>> considered good programming style even if there is an implicit move.
>
> I disagree. This kind of stuff is best left to the compiler. To the code
> writer it doesn't make any difference. The variable is a goner, so it
> can be bound to an rvalue reference anyway. The compiler should be doing
> that. Don't leave that kind of unscalable burden up to all programmers.
> The calls to std::move would be pervasive, and as such they would be at
> best automated.
>
>> And when you start moving into this case:
>> void foo()
>> {
>>     A a;
>>     g(a);
>>     if (condition)
>>        h(a);
>> }
>> things get fuzzier.  An implicit move on h(a) is probably not
>> detectable.  But if I wanted 'a' moving in here I would end up
>> coding the following anyway for higher performance:
>> void foo()
>> {
>>     A a;
>>     if (condition)
>>     {
>>        g(a);
>>        h(std::move(a));
>>     }
>>     else
>>        g(std::move(a));
>> }
>> If we had the last reference rule, one might argue you could
>> simplify that to:
>> void foo()
>> {
>>     A a;
>>     if (condition)
>>     {
>>        g(a);
>>        h(a);
>>     }
>>     else
>>        g(a);
>> }
>> But that is so weird looking, that a maintenance programmer is
>> likely to further simplify that back to:
>> void foo()
>> {
>>     A a;
>>     g(a);
>>     if (condition)
>>        h(a);
>> }
>> !!! :-)
>
> I'm not sure I understand this point. With the implicit move, the
> optimal code is generated in all cases.

There is no optimal code in all cases.  It depends whether you care
more about speed or code size, sorta.  Ignoring cache-locality issues
for the moment, if you want speed your compiler will always turn

  void foo()
  {
      A a;
      g(a);
      if (condition)
          h(a);
  }

Into

  void foo()
  {
      A a;
      if (condition)
      {
         g(a);
         h(std::move(a));
      }
      else
         g(std::move(a));
  }

So now what happens with

  void foo()
  {
      A a;
      g(a);
      if (condition1)
      {
          h(a);
          if (condition2)
          {
              i(a);
              if (condition3)
                  j(a);
          }
      }
  }

??  I smell a code explosion coming on.

>> One cool thing about putting this into the throw statement:  It
>> makes practical movable but non-copyable exception objects.  That to
>> me seems very motivating - worth fighting for.
>
> Yes, because the throw process can be rendered guaranteed nothrow.

Yeah, that's a good thing.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Wed, 20 Jul 2005 15:09:02 GMT
Raw View
In article <IJwG24.1HF6@beaver.cs.washington.edu>,
 "Andrei Alexandrescu (See Website For Email)"
 <SeeWebsiteForEmail@moderncppdesign.com> wrote:

> Under my suggested enhancement, your example above won't wrongly bind a
> to an A&&. It will fail to compile. (It could be made to work if we
> automatically create a copy when passing a.)

Ouch.  That breaks some code that I've really been wanting to make work
(and does work currently on my development machine):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html#Use%20
of%20Rvalue%20Streams

#include <ostream>

class A
{
public:
    A() : data_(0) {}
private:
    int data_;

    friend std::ostream& operator<<(std::ostream&& os, const A& a);
};

std::ostream&
operator<<(std::ostream&& os, const A& a)
{
    return os << a.data_;
}

#include <iostream>
#include <fstream>

int main()
{
  A a;
  std::ofstream("log") << a; // rvalue stream binds to ostream&&
  std::cout << a;            // lvalue stream binds to ostream&&
}

One operator<< is all I have to write to make my class usable with both
lvalue and rvalue streams.  If lvalues won't bind to a non-overloaded
rvalue reference then now I have to write two operator<< every time I
want to make my class streamable:

std::ostream&
operator<<(std::ostream&& os, const A& a) // for rvalue streams
{
    return os << a.data_;
}

std::ostream&
operator<<(std::ostream& os, const A& a) // for lvalue streams
{
    return os << a.data_;
}

Since the algorithm is always the same, I would probably always end up
forwarding one to the other:

std::ostream&
operator<<(std::ostream& os, const A& a) // for lvalue streams
{
    return os << a.data_;
}

std::ostream&
inline
operator<<(std::ostream&& os, const A& a) // for rvalue streams
{
    return static_cast<std::ostream&>(os) << a;
}

<ug> This direction doesn't seem like a step forward for the C++
programmer.

> Do you agree that a
> "goner" can be bound to an rvalue reference - and not only in the
> particular case of return or throw? That's
>
> "Goner", n. - an object that will be destroyed right after the current
> operation.

I think it could be done.  I don't think it is a particularly good idea.
At the very least, it is not compelling.

> Aha! Can you think of an example when introducing std::move would harm
> when passing something to a function?
>
> void foo()
> {
>      A a;
>      goo(std::move(a)); // worse than goo(a);?
> }

Nope.

> I understand your point. However, if the optimization can be done
> without harm, why not allow it?

It is a ton more work, a major change late in the game, and the
"benefit" is going to end up being something that somebody's C++ book is
going to say "don't do this".  I can hear Scott Meyers salivating at the
opportunity! ;-)

> > And when you start moving into this case:
> >

#1
> > void foo()
> > {
> >     A a;
> >     g(a);
> >     if (condition)
> >        h(a);
> > }
> >
> > things get fuzzier.  An implicit move on h(a) is probably not
> > detectable.  But if I wanted 'a' moving in here I would end up coding
> > the following anyway for higher performance:
> >

#2
> > void foo()
> > {
> >     A a;
> >     if (condition)
> >     {
> >        g(a);
> >        h(std::move(a));
> >     }
> >     else
> >        g(std::move(a));
> > }
> >
> > If we had the last reference rule, one might argue you could simplify
> > that to:
> >

#3
> > void foo()
> > {
> >     A a;
> >     if (condition)
> >     {
> >        g(a);
> >        h(a);
> >     }
> >     else
> >        g(a);
> > }
> >
> > But that is so weird looking, that a maintenance programmer is likely to
> > further simplify that back to:
> >

#4
> > void foo()
> > {
> >     A a;
> >     g(a);
> >     if (condition)
> >        h(a);
> > }
> >
> > !!! :-)
>
> I'm not sure I understand this point. With the implicit move, the
> optimal code is generated in all cases.

Well let's go over it again.  This is a crucial point.

In the original code (#1), only h(a) has "last reference" status, and so
only the implicit move can be generated there.

However, as the programmer I know that if I find condition to be false
at run time, then g(a) is actually the last reference.  So I rewrite and
come up with #2 using explicit moves.  This is now faster than #1 for
the case that condition is false, and the same speed as #1 for the case
that condition is true.

Now you tell me that I really don't need the explicit moves with this
rewrite because there are now two last references, one in each branch of
the if statement.  So I could code it as shown in #3 (using implicit
moves at the last reference in each if-branch).

#3 is equivalent to #2 but looks curious.  I'm not sure there is enough
training time left in the 21'st century to educate most C++ programmers
that #3 and #4 are no longer equivalent.  So after I write #3, and leave
the job, there is a very good chance that #3 is going to collapse back
to #4 in a future rewrite.  #3 is just too subtle.

Once I'm back at #4, I'm back where I started - with suboptimal code for
the case when condition is false.  If I had stuck with rewrite #2 (with
explicit moves), chances are very, very much higher that my optimized
code would survive future maintenance.

Moral:  Depending upon these implicit moves would be poor programming
practice, at least in this particular case.

Result:  Why do I want to pour my blood, sweat and tears into something
that I'm going to advise people not to use?  Or at the very least I'd
have to advise people to only use it only in very strict circumstances
(like no branching).  All just so that someone doesn't have to type
std::move(a).  Just teaching people when it is safe to use implicit
moves and when it isn't would be a headache.  Just doesn't seem worth it.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: caj@cs.york.ac.uk (chris jefferson)
Date: Wed, 20 Jul 2005 15:05:07 GMT
Raw View
Andrei Alexandrescu (See Website For Email) wrote:
>>> Addendum:
>>>
>>> I also realize that the notion of binding the last reference of a
>>> named object to an rvalue also applies to the case when the name is
>>> an actual lvalue. Consider:
>>>
>>> struct A {};
>>>
>>> void h(A&);
>>> void h(A&&);
>>>
>>> void g(A&);
>>> void g(A&&);
>>>
>>> void f()
>>> {
>>>     A a; // lvalue
>>>     g(a);  // calls g(A&)
>>>     h(a);  // calls h(A&&)
>>> }
>>>
>>> The same three options apply to this case.
>>>
>>...
>
> Great. The point I am trying to push is that returning an automatic
> variable is only a simple particular case of the general concept behind
> moving, which is: the last access to a variable is moveable.
>
(sorry for cutting the conversation up a little..)

One minor pendantic / stupid point..

Imagine all of A(A&), A(A&&), h and g are defined in different files.
Then I don't think this optimisation is valid.

I could, during copy constructing the copy of a for the call to g,grab a
reference to it. I could then refer to in h.

Of course, you might want to say that either a) you aren't allowed to
write such code, or b) you will only do this optimisation if you can
check A(A&&), or c) (not unlikely) I've just written something invalid...

Chris

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Wed, 20 Jul 2005 10:08:24 CST
Raw View
Andrei Alexandrescu (See Website For Email) wrote:

> My idea is this: The last statically-known access to a value /within its
> lifetime/ is bindable to an rvalue reference.

I don't have much to contribute at this time, because I'm still
thinking about the implications of the idea. But I wanted to say that
it looks interesting, maybe even promising.

One limited subset of the optimizations that you want to allow can be
enabled by something along the lines of:

"A copy that is the last access to an lvalue may be replaced by a
move."

This takes care of RVO/throw/pass by value, but doesn't work for
string::operator+, for example.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Wed, 20 Jul 2005 19:01:39 GMT
Raw View
David Abrahams wrote:
> "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes:
>
>
>>My idea is this: The last statically-known access to a value /within its
>>lifetime/ is bindable to an rvalue reference.
>
>
> But none other, apparently?  That's a major difference from our
> proposal.

Nonono. The rule above only extends (and partially overlaps with) the
current proposal. For example, you can obtain an rvalue reference via a
cast, even though you can freely access the (probably pilfered) value
after issuing the cast.

>>In other words: if after f(a) the only thing you'll ever do will be to
>>destroy a, you can bind a to an rvalue reference because it's a goner
>>anyway - much like a returned value.
>>
>>Under my suggested enhancement, your example above won't wrongly bind a
>>to an A&&. It will fail to compile. (It could be made to work if we
>>automatically create a copy when passing a.)
>
>
> Then it's not really an enhancement; it's a trade-off.  In our
> proposal, that compiles.  That's how we handle the stream output
> function that accepts ostream rvalues and lvalues.  Streams shouldn't
> be copyable, so you can't make it work by copying the stream, and you
> need to be able to pass the same stream to the function multiple times
> in the same scope.

Oh ok, I see. So I think the state of affairs is like this: the current
proposal always considers a named rvalue reference, an lvalue. My
suggestion pushes the idea that the last access of such an rvalue
reference, together with the last access before destruction of an
lvalue, can be bound to an rvalue reference as well.

>>>Otoh, if there is no visibility, I'm all for eliminating those
>>>"moves" where one would naturally want them 100% of the time anyway
>>
>>Awesome. That's the spirit. Exactly what I want too. Do you agree
>>that a "goner" can be bound to an rvalue reference - and not only in
>>the particular case of return or throw?
>
>
> That _can_ already happen in our proposal.  However, it's not
> guaranteed to happen, and rvalue references can also bind to
> non-goners.  What you want, IIUC, is:
>
>   An rvalue reference binds to a named object (in preference to lvalue
>   references) if and only if the object is a goner.

Nonono. There's the cast, too. If you add the cast, I believe the rule
is consistent.

> It's an attractively simple idea, but I'm not sure that it actually
> works.  To be sure, one would have to go through
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html and
> see how well each case was handled.  In particular, you'd have to keep
> the deduction and reference-collapsing rules in place for template
> <class T> f( ... T&&...) arguments.  And you would have to decide that
> the case where you want one non-templated overload to handle non-const
> lvalues and rvalues is not so important, or find another way to handle
> it.

Yes, I will need to do that once we reach an agreement at the intuitive
level. This discussion is useflu because, for example, it weeded out
early a flaw in my reasoning.

[Howard wrote triple-quoted:]
>>>And when you start moving into this case:
>>>void foo()
>>>{
>>>    A a;
>>>    g(a);
>>>    if (condition)
>>>       h(a);
>>>}
>>>things get fuzzier.  An implicit move on h(a) is probably not
>>>detectable.  But if I wanted 'a' moving in here I would end up
>>>coding the following anyway for higher performance:
>>>void foo()
>>>{
>>>    A a;
>>>    if (condition)
>>>    {
>>>       g(a);
>>>       h(std::move(a));
>>>    }
>>>    else
>>>       g(std::move(a));
>>>}
>>>If we had the last reference rule, one might argue you could
>>>simplify that to:
>>>void foo()
>>>{
>>>    A a;
>>>    if (condition)
>>>    {
>>>       g(a);
>>>       h(a);
>>>    }
>>>    else
>>>       g(a);
>>>}
>>>But that is so weird looking, that a maintenance programmer is
>>>likely to further simplify that back to:
>>>void foo()
>>>{
>>>    A a;
>>>    g(a);
>>>    if (condition)
>>>       h(a);
>>>}
>>>!!! :-)

Now I finally understand Howard's point. It's a valid one: because the
move is implicit and flow-dependent, a maintenance programmer might
rewrite code in a way that's less efficient although it looks equivalent.

Hmmm... that's a good point.

> There is no optimal code in all cases.  It depends whether you care
> more about speed or code size, sorta.  Ignoring cache-locality issues
> for the moment, if you want speed your compiler will always turn
>
>   void foo()
>   {
>       A a;
>       g(a);
>       if (condition)
>           h(a);
>   }
>
> Into
>
>   void foo()
>   {
>       A a;
>       if (condition)
>       {
>          g(a);
>          h(std::move(a));
>       }
>       else
>          g(std::move(a));
>   }
>
> So now what happens with
>
>   void foo()
>   {
>       A a;
>       g(a);
>       if (condition1)
>       {
>           h(a);
>           if (condition2)
>           {
>               i(a);
>               if (condition3)
>                   j(a);
>           }
>       }
>   }
>
> ??  I smell a code explosion coming on.

Well, you makes your choices and takes your chances :o). It's not easy.
I think it's nice, then, that the compiler applies a simple conservative
analysis to generate code that's a tad better without you doing
absolutely anything. In the case above, by the rules I suggest, the
generated code is:

   void foo()
   {
       A a;
       g(a);
       if (condition1)
       {
           h(a);
           if (condition2)
           {
               i(a);
               if (condition3)
                   j(static_cast<A&&>(a));
           }
       }
   }

So, if anything, the example above is an argument in favor of my
suggestion: without the programmer doing anything, better code is
generated. This leads to a very nice state of affairs: if you change
class A to take advantage of the new feature, only recompiling A's
client code will be a lot more efficient witout touching it. With the
current proposal, only a fraction of the potential improvements will be
reaped. Then you must go and change client code by inserting std::move
if you want to reap more.

That's quite a nice argument in favor of automatically inserting
static_cast<A&&> where safe.

>>>One cool thing about putting this into the throw statement:  It
>>>makes practical movable but non-copyable exception objects.  That to
>>>me seems very motivating - worth fighting for.
>>
>>Yes, because the throw process can be rendered guaranteed nothrow.
>
>
> Yeah, that's a good thing.

Cool!


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com>
Date: 20 Jul 2005 19:10:03 GMT
Raw View
chris jefferson wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>Great. The point I am trying to push is that returning an automatic
>>variable is only a simple particular case of the general concept behind
>>moving, which is: the last access to a variable is moveable.
 >
> Imagine all of A(A&), A(A&&), h and g are defined in different files.
> Then I don't think this optimisation is valid.
>
> I could, during copy constructing the copy of a for the call to g,grab a
> reference to it. I could then refer to in h.
>
> Of course, you might want to say that either a) you aren't allowed to
> write such code, or b) you will only do this optimisation if you can
> check A(A&&), or c) (not unlikely) I've just written something invalid...

That's a great point. To illustrate:

struct A {};
A * pA;

void h(A&)
{
     ... don't pilfer a ...
}

void h(A&& a)
{
     ... pilfer a ...
     ... read *pA ...
}

void g(A& a)
{
     pA = &a;
}

void g(A&& a);

void f()
{
     A a; // lvalue
     g(a);  // calls g(A&)
     h(a);  // calls h(A&&)
     ... read *pA ...
}

Imagine the implementation are distributed across compilation units.
This is entirely valid code, and would have different semantics under
the existing proposal and under my suggested addendum. (It wouldn't
break any existing code.) The question is, is the new semantics harmful
? For what I can tell, it is typesafe. My semantics will pilfer *pA, but
many aliasing cases have similar effects. Is that bad?


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 20 Jul 2005 21:01:52 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com (Andrei Alexandrescu (See Website
For Email)) wrote (abridged):
> In other words: if after f(a) the only thing you'll ever do will be to
> destroy a, you can bind a to an rvalue reference because it's a goner
> anyway - much like a returned value.
> [...]
> "Goner", n. - an object that will be destroyed right after the current
> operation.

But as I understand it, the object wouldn't have been destroyed right
after the current operation. It would have been destroyed at end of scope,
which might not be for ages. The difference can be significant if we are
using RAII.

For example, suppose File closes itself in its destructor. Then:

     void use( const File &out ) {
         out << "test";
         do_slow_stuff();
     }

     void write_to( File &&out ) {
         out << "test";
         out.close();
         do_slow_stuff();
     }

If I'm following this correctly, it's a reasonable use of && for the
second version of write_to() to close the file, because it knows the
caller has finished with it. In particular, it can close it before
do_slow_stuff() rather than afterwards as would happen with the const&
version. With the original proposal this would be a safe optimisation
because the caller has necessarily finished with the file.

Now consider:

     void demo() {
         File lock( "lock" );
         write_to( lock ); // Last reference!
         File fred( "fred" );
         read_from( fred );
     }

You are claiming that demo() has finished with file "lock" at the
commented line, because there are no more references to it. However, there
is actually another reference, namely lock's destructor, which will be
called at end of scope. And this matters; the author may have been
expecting "lock" to remain open while "fred" is opened and closed.

I'm not arguing for or against anything; I'm just wondering if this is
something you have considered and approve of.

-- Dave Harris, Nottingham, UK.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 21:22:50 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> David Abrahams wrote:
>> "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes:
>>
>>> My idea is this: The last statically-known access to a value
>>> /within its lifetime/ is bindable to an rvalue reference.
>>
>> But none other, apparently?  That's a major difference from our
>> proposal.
>
> Nonono. The rule above only extends (and partially overlaps with)
> the current proposal.

Well, unless I misunderstand you, I think are agreeing below ("ok, I
see") that what you're proposing does not only extend.

> For example, you can obtain an rvalue
> reference via a cast,

Yeah, I was unintentionally not considering casts, so that doesn't
change my picture of what you're proposing at all.

> even though you can freely access the (probably pilfered) value
> after issuing the cast.

Well, if that's the only other way to bind an rvalue reference to an
lvalue, it's pretty much what I thought you were saying.

>>> In other words: if after f(a) the only thing you'll ever do will be
>>> to destroy a, you can bind a to an rvalue reference because it's a
>>> goner anyway - much like a returned value.
>>>
>>> Under my suggested enhancement, your example above won't wrongly
>>> bind a to an A&&. It will fail to compile. (It could be made to
>>> work if we automatically create a copy when passing a.)
>>
>> Then it's not really an enhancement; it's a trade-off.  In our
>> proposal, that compiles.  That's how we handle the stream output
>> function that accepts ostream rvalues and lvalues.  Streams
>> shouldn't be copyable, so you can't make it work by copying the
>> stream, and you need to be able to pass the same stream to the
>> function multiple times in the same scope.
>
> Oh ok, I see.

I hope so, but...

> So I think the state of affairs is like this: the current proposal
> always considers a named rvalue reference, an lvalue.

..yes, but that has little to do with the point you just said "I see"
to.

> My suggestion pushes the idea that the last access of such
> an rvalue reference, together with the last access before
> destruction of an lvalue, can be bound to an rvalue reference as
> well.

Yes, I know that's what you're pushing.  I just don't think you've
spent sufficient effort thinking about how that idea interacts with
everything else.  It doesn't work unless rvalue references only bind
when you're really done with an object (either because it was a
temporary, a named object that's about to go out of scope, or, yes,
because of a cast).  So that part of your proposal makes me
uncomfortable, because our rvalue references will bind to a regular
lvalue that isn't about to evaporate.

I will admit again that the simplicity is very attractive.  I
personally have always been uncomfortable with the idea that we have
something called an rvalue reference that will implicitly bind to
lvalues.  That said, I thought the "ctors by value + copy elision
adjustments" idea was very attractive in its simplicity, and it turned
out to be a dead end.  So I want you to work out the details before I
buy into it.

Actually, this is just part A of what you're proposing.  I'm actually
way more comfortable with part B.

>>>>Otoh, if there is no visibility, I'm all for eliminating those
>>>>"moves" where one would naturally want them 100% of the time anyway
>>>
>>>Awesome. That's the spirit. Exactly what I want too. Do you agree
>>>that a "goner" can be bound to an rvalue reference - and not only in
>>> the particular case of return or throw?
>>
>> That _can_ already happen in our proposal.  However, it's not
>> guaranteed to happen, and rvalue references can also bind to
>> non-goners.  What you want, IIUC, is:
>>
>>   An rvalue reference binds to a named object (in preference to
>>   lvalue references) if and only if the object is a goner.
>
> Nonono. There's the cast, too.

Yeah, yeah, the cast.

> If you add the cast, I believe the rule is consistent.

I never said it wasn't *consistent*.  Consistent with what, anyway?

>> It's an attractively simple idea, but I'm not sure that it actually
>> works.  To be sure, one would have to go through
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html and
>> see how well each case was handled.  In particular, you'd have to keep
>> the deduction and reference-collapsing rules in place for template
>> <class T> f( ... T&&...) arguments.  And you would have to decide that
>> the case where you want one non-templated overload to handle non-const
>> lvalues and rvalues is not so important, or find another way to handle
>> it.
>
> Yes, I will need to do that once we reach an agreement at the intuitive
> level.

For Howard at least, the case where one non-templated overload handles
both non-const lvalues and rvalues is an important one.  So it's a
fundamental change in the way things go, and I'm not sure we can put
off that decision.

> Now I finally understand Howard's point. It's a valid one: because the
> move is implicit and flow-dependent, a maintenance programmer might
> rewrite code in a way that's less efficient although it looks equivalent.
>
> Hmmm... that's a good point.

Yeah, I can't tell whether you missed my point though:

>> There is no optimal code in all cases.  It depends whether you care
>> more about speed or code size, sorta.  Ignoring cache-locality issues
>> for the moment, if you want speed your compiler will always turn
>>   void foo()
>>   {
>>       A a;
>>       g(a);
>>       if (condition)
>>           h(a);
>>   }
>> Into   void foo()
>>   {
>>       A a;
>>       if (condition)
>>       {
>>          g(a);
>>          h(std::move(a));
>>       }
>>       else
>>          g(std::move(a));
>>   }
>> So now what happens with
>>   void foo()
>>   {
>>       A a;
>>       g(a);
>>       if (condition1)       {
>>           h(a);
>>           if (condition2)
>>           {
>>               i(a);
>>               if (condition3)
>>                   j(a);
>>           }
>>       }
>>   }
>> ??  I smell a code explosion coming on.
>
> Well, you makes your choices and takes your chances :o). It's not
> easy.  I think it's nice, then, that the compiler applies a simple
> conservative analysis to generate code that's a tad better without
> you doing absolutely anything.  In the case above, by the rules I
> suggest, the generated code is:
>
>   void foo()
>   {
>       A a;
>       g(a);
>       if (condition1)
>       {
>           h(a);
>           if (condition2)
>           {
>               i(a);
>               if (condition3)
>                   j(static_cast<A&&>(a));
>           }
>       }
>   }

So then the compiler doesn't turn

  void foo()
  {
      A a;
      g(a);
      if (condition)
          h(a);
  }

Into

  void foo()
  {
      A a;
      if (condition)
      {
         g(a);
         h(std::move(a));
      }
      else
         g(std::move(a));
  }

But only

  void foo()
  {
      A a;
      g(a);
      if (condition)
          h(std::move(a));
  }


??

> So, if anything, the example above is an argument in favor of my
> suggestion: without the programmer doing anything, better code is
> generated. This leads to a very nice state of affairs: if you change
> class A to take advantage of the new feature, only recompiling A's
> client code will be a lot more efficient witout touching it. With the
> current proposal, only a fraction of the potential improvements will be
> reaped.

"Only" a very large fraction.

> Then you must go and change client code by inserting std::move
> if you want to reap more.

std::move means "move if possible, else copy," so you can insert
std::move pre-emptively.  And in generic code, you will.  Well of
course that seems dumb; the compiler should just do it for you.
Anyway, I'm calling the case where a local variable is about to go out
of scope part B, and I'm a lot more comfortable with it than with part
A.

> That's quite a nice argument in favor of automatically inserting
> static_cast<A&&> where safe.

Of course there can be no technical argument against it (although my
procedural concerns about making _any_ change now stand).  The whole
argument here is about what "where safe" means.  And about the binding
rules for rvalue references, since they're intimately entwined with
the meaning of "where safe."  Until you come to grips with those two
things, I'm going to remain skeptical.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 21:40:15 GMT
Raw View
brangdon@cix.co.uk (Dave Harris) writes:

> Now consider:
>
>      void demo() {
>          File lock( "lock" );
>          write_to( lock ); // Last reference!
>          File fred( "fred" );
>          read_from( fred );
>      }
>
> You are claiming that demo() has finished with file "lock" at the
> commented line, because there are no more references to it. However,
> there is actually another reference, namely lock's destructor, which
> will be called at end of scope. And this matters; the author may
> have been expecting "lock" to remain open while "fred" is opened and
> closed.
>
> I'm not arguing for or against anything; I'm just wondering if this
> is something you have considered and approve of.

Ah, now you see, I was seduced by Andrei's simple-looking idea.  I
hadn't considered that, at all, and IMO it topedoes part B of his idea
that I said I was more comfortable with.

Here's a more compelling example:

  void print(std::string msg)
  {
      std::cout << msg << std::endl;
  }

  void foo()
  {
      std::string greet("hello, world");
      std::string::iterator w = greet.begin() + 6;
      std::string::iterator end = greet.end();

      print(greet);     // last use of greet; Andrei says we can move!!

      // now greet is empty and w and end are invalidated.

      std::string world(w,end); // crash!
      print(world);
  };

Like I've been saying, this stuff is subtler than it looks.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Wed, 20 Jul 2005 21:51:02 GMT
Raw View
Dave Harris wrote:
> SeeWebsiteForEmail@moderncppdesign.com (Andrei Alexandrescu (See Website
> For Email)) wrote (abridged):
>
>>In other words: if after f(a) the only thing you'll ever do will be to
>>destroy a, you can bind a to an rvalue reference because it's a goner
>>anyway - much like a returned value.
>>[...]
>>"Goner", n. - an object that will be destroyed right after the current
>>operation.
>
>
> But as I understand it, the object wouldn't have been destroyed right
> after the current operation.

Oh, sorry. Thanks Dave.

"Goner", n. - an object that provably has destruction as the only use
after the current operation.

There's also a new synonim:

"Goner", n. - a loud-mouthed guy who doesn't spend enough time to
compose precise sentences when writing in a newsgroup. :o)


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Wed, 20 Jul 2005 22:48:29 GMT
Raw View
David Abrahams wrote:
> In case it wasn't totally obvious, I mean to say that an rvalue
> reference can also be bound to an lvalue.  Much of what you've
> proposed up to this point in the thread seems to be predicated on the
> idea that when you have an rvalue reference to an object, it means
> nobody else is going to touch it.  That, my friend, is a major
> departure.

What I mean is that when you have an rvalue OR lvalue reference to an
object, the last operation before destruction of that object ought to be
allowed to pilfer it. The current proposal allows that only on return.
We agreed it should on throw, too, and I want to convince you that it
should on a function call, too.

Now when you have a named rvalue reference coming down to a function,
you are allowed to pilfer that within the function, because either it
comes from a temporary that will be destroyed after the function call,
or is the result of a cast/std::move that also can be pilfered - the
caller expects that.

> When you ask why not work together, I guess my answer is that I am
> afraid to get into a project with someone who doesn't appreciate how
> hard the problem is.  I could, of course, be totally wrong about your
> point-of-view (or for that matter how hard the problem is), but so far
> that's my impression.

I understand. It is clear that a lot of effort has gone into the current
proposal. Again, it's clarified a ton of things for me. The one aspect
that I'm thinking of for a long time and didn't find in there, was
giving the freedom to the compiler to treat the last reference to a
value as "disposable".


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Wed, 20 Jul 2005 22:48:32 GMT
Raw View
Howard Hinnant wrote:
>  "Andrei Alexandrescu (See Website For Email)"
>  <SeeWebsiteForEmail@moderncppdesign.com> wrote:
>>Under my suggested enhancement, your example above won't wrongly bind a
>>to an A&&. It will fail to compile. (It could be made to work if we
>>automatically create a copy when passing a.)
>
>
> Ouch.  That breaks some code that I've really been wanting to make work
> (and does work currently on my development machine):

I need to rethink that. A named rvalue reference should be bindable back
to an lvalue reference.

> <ug> This direction doesn't seem like a step forward for the C++
> programmer.

Oui. I agree.,

>>Do you agree that a
>>"goner" can be bound to an rvalue reference - and not only in the
>>particular case of return or throw? That's
>>
>>"Goner", n. - an object that will be destroyed right after the current
>>operation.
>
>
> I think it could be done.  I don't think it is a particularly good idea.
> At the very least, it is not compelling.

:o(

My proposal (after we iron away its problems) yields better code than
yours after only changing a class and recompiling. Yours also yields
better code, but not as good as mine. To reap what my proposal brings,
you'd have to ***do surgery on client code*** to insert the std::move
where you believe it is appropriate. Is that good style?!? Good style
*is* modular improvement, not requiring non-scalable code edits. In my
proposal, the compiler will diligently and safely insert the move
wherever it is appropriate.

>>I understand your point. However, if the optimization can be done
>>without harm, why not allow it?
>
>
> It is a ton more work, a major change late in the game, and the
> "benefit" is going to end up being something that somebody's C++ book is
> going to say "don't do this".  I can hear Scott Meyers salivating at the
> opportunity! ;-)

It depends on how big a change we are willing to make. For example, I
think everybody agrees that mentioning the throw thing is easy and
worthwile. Now I just want to convince you that it is good style to bind
a goner to an rvalue reference, under conditions of which
restrictiveness we can negotiate. By the way, binding in the case of
return and throw are already instances of the same thing: they do bind
the last access of an lvalue (or a named rvalue) to an rvalue reference.
The only difference is that they are easily detectable without any
analysis - they obviously *are* the last access.

Now I will try to slowly talk you into less and less restrictive cases.
Imagine this:

int f(A&&);
int f(A&);

int g()
{
   A a;
   return f(a);
}

Would you agree that it would be nice to pass a to f(A&&)? We, of
course, assume that f(A&&) is more efficient than f(A&) since it has the
freedom of pilfering its argument however it wants, in addition to doing
whatever f(A&0 does.

So, if you allow "return a" to bind to an rvalue reference, why wouldn't
you also allow "return f(a)"?

A counter-argument is that if we allow arbitrary expressions, then
aliasing can pose problems. It would be nice, however, that at least in
the cases in which the compiler can prove there is no aliasing, it it
allowed to reap that opportunity for optimization.

So what I'm saying is that a general and safe rule is: "in a simple
enough expression in a return or throw statement, a local lvalue, or an
rvalue reference, can be bound to a rvalue reference." This is the
general case. The issue is how to define "simple enough". The current
definition is "id", where id is the name of a local lvalue.

Also, the current proposal (if I understand it correctly) never allows
rebinding automatically an incoming named rvalue reference back to an
rvalue when returning it, which I believe is a problem. With the current
proposal, even writing an identity function is gauche:

template <class T>
inline
T&&
identity(T&& x)
{
     return std::move(x);
}
// plus other overloads

when at best we'd allow the obvious way of writing identity:

template <class T>
inline
T&&
identity(T&& x)
{
     return x;
}
// plus other overloads with IDENTICAL body

Now, assuming that I convinced you that rules about return could be
improved, allow me to tell you about those leather seats and stereo
system in your wonderful new used car.

You made a good case that flow-dependency can be confusing. Thanks for
explaining it so thoroughly, that even an idiot could understand. I
finally did :o).

Ok, let's eliminate flow dependence and posit that "if there is a last
*unconditional* access to a named rvalue or local lvalue, then that
access can be bound to an lvalue reference".

That would successfully eliminate the "confusing flow-dependent
rewritings". Consider:

int f(A&&);
int f(A&);

void g()
{
   A a;
   f(a); // calls f(A&); not an unconditional last access
   if (condition) {
      f(a); // calls f(A&); not an unconditional last access
   }
}

In a function that has an unconditional last access, cool - a can be moved:

int g()
{
   A a;
   f(a); // calls f(A&); not an unconditional last access
   if (condition) {
      f(a); // calls f(A&); not an unconditional last access
   }
   f(a); // AHA! calls f(A&&); last unconditional access
   return 42; // == the meaning of life
}

Would you believe that that's better?

The only problem that I see that is that people who still like SESE,
with which I'm fighting machine guns against swords in
comp.lang.c++.moderated, will happily say this is yet another case in
favor of SESE :o).

In this form, the compiler will automatically insert the std::move in
some quite conservative, but safe and allegedly often-encountered,
cases, without also inserting confusion in the process. According to
you, that was a crucial point:

> Well let's go over it again.  This is a crucial point.

Now if we assuage the crucial point, could we still talk about that
sunroof too? :o)

> Moral:  Depending upon these implicit moves would be poor programming
> practice, at least in this particular case.

I agree. Flow dependency can confuse people, especially since it's new.

> Result:  Why do I want to pour my blood, sweat and tears into something
> that I'm going to advise people not to use?  Or at the very least I'd
> have to advise people to only use it only in very strict circumstances
> (like no branching).

Aha!!! I only now see that what I'm suggesting is identical to the case
in which you say it would be good! Yay!

Can now we pour a lil blood, sweat, and tears (maybe leave there the
tipe of a nail too) into something that is a scalable and safe optimization?

> All just so that someone doesn't have to type
> std::move(a).  Just teaching people when it is safe to use implicit
> moves and when it isn't would be a headache.  Just doesn't seem worth it.

True. But (a) typing std::move(a) all over is not scalable, and (b) in
the new form, there's no need to explain - in all cases it's not a headache.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Wed, 20 Jul 2005 22:48:28 GMT
Raw View
In article <dbl99n$ob$1@pump1.york.ac.uk>,
 caj@cs.york.ac.uk (chris jefferson) wrote:

> Andrei Alexandrescu (See Website For Email) wrote:
> >>> Addendum:
> >>>
> >>> I also realize that the notion of binding the last reference of a
> >>> named object to an rvalue also applies to the case when the name is
> >>> an actual lvalue. Consider:
> >>>
> >>> struct A {};
> >>>
> >>> void h(A&);
> >>> void h(A&&);
> >>>
> >>> void g(A&);
> >>> void g(A&&);
> >>>
> >>> void f()
> >>> {
> >>>     A a; // lvalue
> >>>     g(a);  // calls g(A&)
> >>>     h(a);  // calls h(A&&)
> >>> }
> >>>
> >>> The same three options apply to this case.
> >>>
> >>...
> >
> > Great. The point I am trying to push is that returning an automatic
> > variable is only a simple particular case of the general concept behind
> > moving, which is: the last access to a variable is moveable.
> >
> (sorry for cutting the conversation up a little..)
>
> One minor pendantic / stupid point..
>
> Imagine all of A(A&), A(A&&), h and g are defined in different files.
> Then I don't think this optimisation is valid.
>
> I could, during copy constructing the copy of a for the call to g,grab a
> reference to it. I could then refer to in h.

I think you have a point.  Trying to rewrite the example to expose it a
little more clearly:

#include <memory>
#include <cassert>

struct do_nothing_deleter
{
    template <class T>
    void operator()(T*) const {}
};

struct A
{
    A() : data_(1) {}
    int data_;
};

void h(A&) {}
void h(A&& a) {a.data_ = 0;}

int main()
{
    A a;
    std::unique_ptr<A, do_nothing_deleter> p(&a);
    h(a);  // last reference?
    assert(p->data_ == 1);  // fires?
}

How does the compiler know that unique_ptr has squirreled away a
reference to 'a', and then is referring back to 'a' in the assert?

This example is a little different from Chris' but not by much.  If g
and h are member functions of the same object, this scenario doesn't
seem that far fetched to me.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 20 Jul 2005 22:51:52 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> Dave Harris wrote:
>> SeeWebsiteForEmail@moderncppdesign.com (Andrei Alexandrescu (See
>> Website For Email)) wrote (abridged):
>>
>>> In other words: if after f(a) the only thing you'll ever do will be
>>> to destroy a, you can bind a to an rvalue reference because it's a
>>> goner anyway - much like a returned value.
>>>[...]
>>> "Goner", n. - an object that will be destroyed right after the
>>> current operation.
>> But as I understand it, the object wouldn't have been destroyed
>> right after the current operation.
>
> Oh, sorry. Thanks Dave.
>
> "Goner", n. - an object that provably has destruction as the only use
> after the current operation.

That can be pretty darned hard to prove.  If the object's constructor
stores a reference to itself somewhere and there are any objects (even
temporaries) constructed afterwards in the same scope, the jig is up,
because any of them could try to use it.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Thu, 21 Jul 2005 03:43:05 GMT
Raw View
David Abrahams wrote:
> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>For example, you can obtain an rvalue
>>reference via a cast,
>
>
> Yeah, I was unintentionally not considering casts, so that doesn't
> change my picture of what you're proposing at all.

Ok, perfect. So we're in good sync now.

> Yes, I know that's what you're pushing.  I just don't think you've
> spent sufficient effort thinking about how that idea interacts with
> everything else.  It doesn't work unless rvalue references only bind
> when you're really done with an object (either because it was a
> temporary, a named object that's about to go out of scope, or, yes,
> because of a cast).  So that part of your proposal makes me
> uncomfortable, because our rvalue references will bind to a regular
> lvalue that isn't about to evaporate.

Ok. I think I made an error.

So now that I've read the random_shuffle example, I can say this: a
parameter of type A&& in a function f gives the information that that
parameter originates either from an rvalue or lvalue. I was wrongly
thinking that a parameter of type A&& gives confidence that we can
pilfer that parameter.

You can only pilfer the parameter in f if you have overloaded f to take
both an A& and an A&&:

f(A&);
f(A&&); // can pilfer because f(A&) exists

Hmmmmm... that simplifies the case "I want to implement a function that
doesn't care whether the source is an rvalue or an lvalue". The paper
makes an argument that there are situation in which you do want that,
such as temporary streams, random_shuffle, etc.

Such a setup, however, shots down some of the optimizations that I
suggest, because at the function level there is a loss of information.
You can't reason in isolation about the nature of a inside f(A&& a).
Actually it's very awkward: you can reason about the nature of a inside
f(A&& a), provided there exists an overload f(A&). I find that
unsettling. This aspect of the proposal is optimization-unfriendly for
the benefit of a feature ("modifiable objects whether or not they
originate for temporaries") that I believe is obscure and can be easily
coded around with a forwarding overload.

Probably the alternative was analyzed and found wanting. The alternative
would be:

f(A&); // attracts lvalues only
f(A&&); // attracts rvalues only
f(const A&); // attracts const values
              // and also rvalues and non-const lvalues as a fallback

That would ask for an overload to handle rvalues and rvalues in one. On
the bright side, it would enable modular reasoning about the parameter
inside f(A&& a) - a is a goner.

Deciding that a is a goner in f(A&& a) iff there exists an overload
f(A&) is awkward.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Thu, 21 Jul 2005 03:41:14 GMT
Raw View
David Abrahams wrote:
> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>"Goner", n. - an object that provably has destruction as the only use
>>after the current operation.
>
>
> That can be pretty darned hard to prove.  If the object's constructor
> stores a reference to itself somewhere and there are any objects (even
> temporaries) constructed afterwards in the same scope, the jig is up,
> because any of them could try to use it.

You got a point. In the case of "return a;" where a is a local, the
proof is sort of trivial because any reference to a escapes (say in a
global variable), it would be illegal anyway. So you don't need to worry
about it since such code already has undefined behavior.

Now, let me ask this. Would you agree to extend rvalue-ref bindability
to "return a;", where "a" is the name of an rvalue reference? As in:

A foo(A&& a)
{
   if (condition) return A(); // bindable to A(A&&)
   return a; // bindable to A(A&&)
}

Or is there no need for thise because RVO can take care of it anyway? Or
  is there some danger in allowing it?


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: jgottman@carolina.rr.com ("Joe Gottman")
Date: Thu, 21 Jul 2005 15:43:22 GMT
Raw View
""Andrei Alexandrescu (See Website For Email)""

> Oh ok, I see. So I think the state of affairs is like this: the current
> proposal always considers a named rvalue reference, an lvalue. My
> suggestion pushes the idea that the last access of such an rvalue
> reference, together with the last access before destruction of an lvalue,
> can be bound to an rvalue reference as well.

 This could have some overly subtle effects on code maintenance.  What
happens I add a new access to the value in question after the last access?
The now second-to-last access must now be treated as an lvalue reference.
Thus I have changed the semantics of that access even though I haven't
touched it.

Also, what if the last access is an assert?  Then, when I change my compiler
setting from DEBUG to RELEASE,  the assert is #defined away and the second
to last access becomes the last access, again invisibly changing the
semantics of code I haven't modified.

Joe Gottman

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Thu, 21 Jul 2005 15:47:35 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> David Abrahams wrote:
>> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:
>>> "Goner", n. - an object that provably has destruction as the only
>>> use after the current operation.
>> That can be pretty darned hard to prove.  If the object's
>> constructor
>> stores a reference to itself somewhere and there are any objects (even
>> temporaries) constructed afterwards in the same scope, the jig is up,
>> because any of them could try to use it.
>
> You got a point. In the case of "return a;" where a is a local, the
> proof is sort of trivial because any reference to a escapes (say in a
> global variable), it would be illegal anyway. So you don't need to worry
> about it since such code already has undefined behavior.
>
> Now, let me ask this. Would you agree to extend rvalue-ref bindability
> to "return a;", where "a" is the name of an rvalue reference?

Not as a simple change to our proposal.  Andrei, it's like you keep
ignoring what I've written over and over again in this thread: in our
proposal, rvalue refs can bind _implicitly_ to lvalues, so pilfering
implicitly from a is no safer than implicitly pilfering from an ordinary
lvalue ref.  Would you suggest that we pilfer in this case?

   A f(A& a) { return a; }

if not, I think you ought to be able to see why changing it to

   A f(A&& a) { return a; }

shouldn't change anything.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Thu, 21 Jul 2005 15:52:06 GMT
Raw View
SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> David Abrahams wrote:
>> SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)") writes:

> So now that I've read the random_shuffle example, I can say this: a
> parameter of type A&& in a function f gives the information that that
> parameter originates either from an rvalue or lvalue.

In other words, no information.

> I was wrongly thinking that a parameter of type A&& gives confidence
> that we can pilfer that parameter.
>
> You can only pilfer the parameter in f if you have overloaded f to take
> both an A& and an A&&:
>
> f(A&);
> f(A&&); // can pilfer because f(A&) exists

Right.

> Hmmmmm... that simplifies the case "I want to implement a function
> that doesn't care whether the source is an rvalue or an lvalue". The
> paper makes an argument that there are situation in which you do
> want that, such as temporary streams, random_shuffle, etc.
>
> Such a setup, however, shots down some of the optimizations that I
> suggest, because at the function level there is a loss of
> information.  You can't reason in isolation about the nature of a
> inside f(A&& a).  Actually it's very awkward: you can reason about
> the nature of a inside f(A&& a), provided there exists an overload
> f(A&).

Yes, I agree in principle that you lose the ability to know that a
reference is pilferable.  But those optimizations are already shot
down for the most part, because the compiler usually can't deduce that
pilferability is possible.  Did you miss the point of Dave Harris'
insight and my example?

  void print(std::string msg)
  {
      std::cout << msg << std::endl;
  }

  void foo()
  {
      std::string greet("hello, world");
      std::string::iterator w = greet.begin() + 6;
      std::string::iterator end = greet.end();

      print(greet);     // last use of greet; Andrei says we can move!!

      // now greet is empty and w and end are invalidated.

      std::string world(w,end); // crash!
      print(world);
  };

The only way to know for sure that pilfering is wrong in this case is
to know something about what begin() and end() mean (and no, it's not
something the compiler can deduce, even if they're inline).  By the
same token, if we replace std::string with mu::fnarg everywhere,
pilfering might be just fine.  Only the programmer can know.  So in
general, the programmer will want to do it explicitly.

> I find that unsettling.

As I posted earlier in this thread, me too.  But only a little, and not
enough to try to perturb the proposal at this point.

> This aspect of the proposal is optimization-unfriendly for the
> benefit of a feature ("modifiable objects whether or not they
> originate for temporaries") that I believe is obscure and can be
> easily coded around with a forwarding overload.

That's something we're trying to avoid.  Once you have multiple
mutable reference arguments that can bind to rvalues as well as
lvalues, the forwarding problem comes back.  Admittedly, this is not
as important an instance the forwarding problem as some of the others,
but getting rid of that problem was a major point of the proposal.

> Probably the alternative was analyzed and found wanting.

Probably, although frankly it's hard to remember everything we
analyzed.  We analyzed a *lot*.

> The alternative would be:
>
> f(A&); // attracts lvalues only
> f(A&&); // attracts rvalues only
> f(const A&); // attracts const values
>              // and also rvalues and non-const lvalues as a fallback
>
> That would ask for an overload to handle rvalues and rvalues in one. On
> the bright side, it would enable modular reasoning about the parameter
> inside f(A&& a) - a is a goner.
>
> Deciding that a is a goner in f(A&& a) iff there exists an overload
> f(A&) is awkward.

But the information that a is a goner isn't all that useful.  Just
change foo above so that it takes greet as an argument by
std::string&& and you'll see why.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Mon, 18 Jul 2005 21:03:29 GMT
Raw View
(Please follow up in comp.std.c++)

Hello,


I'm looking at n1377
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm) and
found an important potential for optimization.

The current proposal says:

-----------------------
Even though named rvalue references can bind to an rvalue, they are
treated as lvalues when used. For example:

struct A {};

void h(const A&);
void h(A&&);

void g(const A&);
void g(A&&);

void f(A&& a)
{
     g(a);  // calls g(const A&)
     h(a);  // calls h(const A&)
}

Although an rvalue can bind to the "a" parameter of f(), once bound, a
is now treated as an lvalue. In particular, calls to the overloaded
functions g() and h() resolve to the const A& (lvalue) overloads.
Treating "a" as an rvalue within f would lead to error prone code: First
the "move version" of g() would be called, which would likely pilfer
"a", and then the pilfered "a" would be sent to the move overload of h().
-----------------------

The argument in favor of selecting the const overload over the rvalue
overload applies to g, but does not apply to h. This is because after h
returns, there is no other use of a - the invocation h(a) is the "last
statically-known access to a". From here on, unless otherwise specified,
when saying "last access" I really mean "last statically-known access".

Assuming that in general a rvalue overload is more efficient than a
const& overload, it follows that there is a good reason for making the
following decision:

void f(A&& a)
{
     g(a);  // calls g(const A&)
     h(a);  // calls h(A&&)
}

The lvalue "a" can be safely "pilfered" when invoking h, because there
is no other access to "a" after h has been invoked.

On the other hand, the last dynamic access is untractable and as such
disallows the move reference. For example:

void f(A&& a)
{
     g(a);  // calls g(const A&)
     h(a);  // calls h(const A&)
     if (false)
     {
         h(a); // calls h(A&&)
     }
}

In other words, the compiler performs "path-insensitive liveness
analysis" to decide what is the last access of a variable.

So it would be a good source of optimization if the last access of a
named rvalue reference would be bindable to an rvalue reference.

Such a rule would also enable better forwarding. Consider the way the
unwary might be thinking of implementing forwarding:

void forwarder(const A& a) {
     g(a);
}

void forwarder(A&& a) {
     g(a);
}

This is the most natural form. The problem is, no matter how you invoke
forwarder, it will *always* invoke g(A&). The current solution is to
prescribe a cast that manually realizes the notion of last access:

void forwarder(const A& a) {
     g(a);
}

void forwarder(A&& a) {
     g(static_cast<A&&>(a));
}

In the setup that I suggest, the first syntax would do what currently
the second syntax does. If the latter is "perfect forwarding", the
former could be called "plusquamperfect forwarding" :o).

There seems to be a problem with the setup that I am suggesting. Consider:

void f(A&& a)
{
     A& b = a; // aliasing the named rvalue ref
     g(a);  // calls g(const A&)
     h(a);  // calls h(A&&)
     h(b); // calls h(A&&) against a pilfered value!
}

It is unclear to me whether in the current proposal named rvalue
references can be assigned to named lvalue references. My current
understanding is that that's possible, because, I quote again, "Even
though named rvalue references can bind to an rvalue, they are treated
as lvalues when used."

The correct behavior of the code above would be:

void f(A&& a)
{
     const A& b = a; // aliasing the named rvalue ref
     g(a);  // calls g(const A&)
     h(a);  // calls h(const A&)
     h(b); // calls h(A&&)
}

However, that calls for an intraprocedural alias analysis which is not
very hard, but a tad more complicated than the liveness analysis.

I still think the suggestion is worthwhile, so I believe it would be
good to looking for solutions to the above issue issue instead of
off-hand rejecting it. The way I see things now, there are the following
possible solutions:

1. Allow compilers to bind the last access to a named rvalue reference
to an rvalue reference discretionary, if they can prove that there are
no other live aliases to that rvalue. This nicely makes the problem a
QoI issue and allows healthy competition for quality code generation
from the compilers.

2. Require compilers to always bind the last access to a named rvalue
reference to an rvalue reference, even in the presence of aliasing.
Then, by design, users who choose to alias an rvalue reference will
expect to see a pilfered value after the last access of the aliased
rvalue reference. This is a sound option that may surprise the
occasional beginner, but it is defensible on the grounds that an rvalue
is not constant and therefore it is quite expected to change when passed
to a function. In this regard it's pretty much the same thing as
aliasing a non-const reference and then passing it to a function - the
function can change it, and nobody blinks an eye.

3. Require compilers to implement interprocedural alias analysis, and
only bind the last access to any alias to an rvalue to an rvalue
reference. This is the safest and most efficient of all options, but it
puts more burden on compiler writers.

I think either of these options have merit. I'd love to hear what other
people think.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Mon, 18 Jul 2005 22:13:00 GMT
Raw View
Addendum:

I also realize that the notion of binding the last reference of a named
object to an rvalue also applies to the case when the name is an actual
lvalue. Consider:

struct A {};

void h(A&);
void h(A&&);

void g(A&);
void g(A&&);

void f()
{
     A a; // lvalue
     g(a);  // calls g(A&)
     h(a);  // calls h(A&&)
}

The same three options apply to this case.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Mon, 18 Jul 2005 22:19:30 GMT
Raw View
In article <42DC1872.2050601@moderncppdesign.com>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> I'm looking at n1377
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm) and
> found an important potential for optimization.

Thank you for your continued interest in this proposal.

> The current proposal says:
>
> -----------------------
> Even though named rvalue references can bind to an rvalue, they are
> treated as lvalues when used. For example:

<snip>

> Assuming that in general a rvalue overload is more efficient than a
> const& overload, it follows that there is a good reason for making the
> following decision:
>
> void f(A&& a)
> {
>      g(a);  // calls g(const A&)
>      h(a);  // calls h(A&&)
> }
>
> The lvalue "a" can be safely "pilfered" when invoking h, because there
> is no other access to "a" after h has been invoked.
>
> On the other hand, the last dynamic access is untractable and as such
> disallows the move reference. For example:
>
> void f(A&& a)
> {
>      g(a);  // calls g(const A&)
>      h(a);  // calls h(const A&)
>      if (false)
>      {
>          h(a); // calls h(A&&)
>      }
> }
>
> In other words, the compiler performs "path-insensitive liveness
> analysis" to decide what is the last access of a variable.

Imho this makes the rules too complicated for programmers reading the
code.  It is important for the human reader of the code to know if
something is treated as an rvalue or not.  There are more reasons to use
&& than just moving and forwarding.  And trying to figure out if your
compiler has just used your named A&& as an lvalue or rvalue should not
require the programmer to quickly do a visual flow analysis on the fly.

In the example f(A&& a) above, what if f() isn't overloaded?  That means
that lvalues can bind to f(A&&).  This further means that whether the
last h(a) calls h(A&&) or h(const A&) is visible to the client.  Thus
this suggestion is no longer a pure optimization, but a change in
observable behavior.  Or the interface of f() must document that if the
condition is true, "a" is moved from, else it isn't.

N1690 contains a few example uses of A&& that involve neither moving nor
forwarding and so I don't believe that this f(A&&) is purely
hypothetical.

> So it would be a good source of optimization if the last access of a
> named rvalue reference would be bindable to an rvalue reference.

C++ is big and complicated.  The rvalue reference proposal while fairly
simple in concept, has an extremely wide ripple effect.  So I'm highly
motivated to keep the basic concept as simple and safe as possible.

If the change you were talking about was not observable, or if it
offered an optimization that was compelling and couldn't be obtained
manually, then I would be strongly motivated to find a way to make it
work.

But coding the following instead:

void f(A&& a)
{
     g(a);  // calls g(const A&)
     h(a);  // calls h(const A&)
     if (false)
     {
         h(std::move(a)); // calls h(A&&)
     }
}

is such a small burden on the author of f() (if that is what is
intended) that the extra complication implied by your suggestion does
not seem worth it.  And perhaps even more importantly, coding:

void f(A&& a)
{
     g(a);  // calls g(const A&)
     h(a);  // calls h(const A&)
     if (false)
     {
         h(a); // calls h(const A&)
     }
}

instead (if that is what the programmer intends) gives the programmer
more options on this observable behavior.  If the compiler goes into
"automatic transmission" mode and starts implicitly moving when it can
be observed, the result is sometimes going to be an extremely irritated
client.

> Such a rule would also enable better forwarding. Consider the way the
> unwary might be thinking of implementing forwarding:
<snip>
> The current solution is to
> prescribe a cast that manually realizes the notion of last access:
>
> void forwarder(const A& a) {
>      g(a);
> }
>
> void forwarder(A&& a) {
>      g(static_cast<A&&>(a));
> }

I'd prefer the more descriptive:

void forwarder(A&& a) {
     g(std::forward<A>(a));
}

Personally I think the explicitness of this earns bonus points (e.g. you
can search for "forward"), especially in the light that A&& doesn't
automatically mean forward.  For example:

struct A {};

std::ostream&
operator<< (std::ostream&& os, const A&)
{
    return os << "A";
}

Here "&&" is used for the ostream even though no moving nor forwarding
is intended.  The reason for the "&&" is simply so clients can use
rvalue streams:

A a;
..
std::ofstream("filename") << a;

<snip>

>Consider:
>
> void f(A&& a)
> {
>      A& b = a; // aliasing the named rvalue ref
>      g(a);  // calls g(const A&)
>      h(a);  // calls h(A&&)
>      h(b); // calls h(A&&) against a pilfered value!
> }
>
> It is unclear to me whether in the current proposal named rvalue
> references can be assigned to named lvalue references. My current
> understanding is that that's possible, because, I quote again, "Even
> though named rvalue references can bind to an rvalue, they are treated
> as lvalues when used."

That is correct.  The example code above is legal, but all calls resolve
to g(const A&) and h(const A&).

> I still think the suggestion is worthwhile, so I believe it would be
> good to looking for solutions to the above issue issue instead of
> off-hand rejecting it. The way I see things now, there are the following
> possible solutions:
>
> 1. Allow compilers to bind the last access to a named rvalue reference
> to an rvalue reference discretionary, if they can prove that there are
> no other live aliases to that rvalue. This nicely makes the problem a
> QoI issue and allows healthy competition for quality code generation
> from the compilers.
>
> 2. Require compilers to always bind the last access to a named rvalue
> reference to an rvalue reference, even in the presence of aliasing.
> Then, by design, users who choose to alias an rvalue reference will
> expect to see a pilfered value after the last access of the aliased
> rvalue reference. This is a sound option that may surprise the
> occasional beginner, but it is defensible on the grounds that an rvalue
> is not constant and therefore it is quite expected to change when passed
> to a function. In this regard it's pretty much the same thing as
> aliasing a non-const reference and then passing it to a function - the
> function can change it, and nobody blinks an eye.
>
> 3. Require compilers to implement interprocedural alias analysis, and
> only bind the last access to any alias to an rvalue to an rvalue
> reference. This is the safest and most efficient of all options, but it
> puts more burden on compiler writers.
>
> I think either of these options have merit. I'd love to hear what other
> people think.

I think all of them will end up requiring IPA unless we want to expose
surprising (observable) behavior in some cases.  And I really don't
think we want to require IPA in the standard (although I'm a big fan of
IPA).  And I really don't want to see observable behavior change
depending on whether or not IPA was done, and to what quality it was
done.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Mon, 18 Jul 2005 23:09:16 GMT
Raw View
In article <IJuFKo.H75@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> Addendum:
>
> I also realize that the notion of binding the last reference of a named
> object to an rvalue also applies to the case when the name is an actual
> lvalue. Consider:
>
> struct A {};
>
> void h(A&);
> void h(A&&);
>
> void g(A&);
> void g(A&&);
>
> void f()
> {
>      A a; // lvalue
>      g(a);  // calls g(A&)
>      h(a);  // calls h(A&&)
> }
>
> The same three options apply to this case.

This particular scenario is more interesting and quite similar to the
already proposed implicit move on return:

A f()
{
     A a; // lvalue
     g(a);  // calls g(A&)
     return a;  // as if return std::move(a)
}

The implicit cast to rvalue is only allowed for those cases where RVO is
already allowed: i.e. when the variable has non-cv-qualified automatic
storage duration and the return type is the same type as the variable
returned.

However in your case:

void f()
{
     A a; // lvalue
     g(a);  // calls g(A&)
     h(a);  // calls h(A&&)
}

What if only h(A&) exists?  Does this change then not break existing
code?  I suppose the compiler could back off of the implicit cast to
rvalue if only h(A&) exists (indeed this is what is currently proposed
for the return statement in N1770).

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 19 Jul 2005 00:06:50 GMT
Raw View
Howard Hinnant wrote:
> Imho this makes the rules too complicated for programmers reading the
> code.  It is important for the human reader of the code to know if
> something is treated as an rvalue or not.  There are more reasons to use
> && than just moving and forwarding.  And trying to figure out if your
> compiler has just used your named A&& as an lvalue or rvalue should not
> require the programmer to quickly do a visual flow analysis on the fly.

It is true that there is more complication. However (1) if lvalue
references are just an more conservative version of rvalue references,
then conversely rvalue references are only an optimization of lvalue
references so whether the compiler chooses one or the other is not of
high relevance. After all, nobody blinks an eye today when the compiler
discretionary eliminates calls to copy constructors because it assumes
what they do.

> In the example f(A&& a) above, what if f() isn't overloaded?  That means
> that lvalues can bind to f(A&&).  This further means that whether the
> last h(a) calls h(A&&) or h(const A&) is visible to the client.  Thus
> this suggestion is no longer a pure optimization, but a change in
> observable behavior.  Or the interface of f() must document that if the
> condition is true, "a" is moved from, else it isn't.

I'm not sure I understand. Could you post an example? The "above" code
that I'm seeing is:

void f(A&& a)
{
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (false)
      {
          h(a); // calls h(A&&)
      }
}

So... where's the change in observable behavior?

I will skip for now the discussion referring to this point coz I can't
understand it.

> If the change you were talking about was not observable, or if it
> offered an optimization that was compelling and couldn't be obtained
> manually, then I would be strongly motivated to find a way to make it
> work.

I believe the observability is achievable, or, in the unlikely event
it's you and not me who misunderstood something, already achieved.

> But coding the following instead:
>
> void f(A&& a)
> {
>      g(a);  // calls g(const A&)
>      h(a);  // calls h(const A&)
>      if (false)
>      {
>          h(std::move(a)); // calls h(A&&)
>      }
> }
>
> is such a small burden on the author of f() (if that is what is
> intended) that the extra complication implied by your suggestion does
> not seem worth it.

By the same token it could be argued that with move semantics in C++98
people could be asked to insert std::move where appropriate without any
change in the core language. You know very well there is burden. There
is burden here, too. I don't want to insert move. I want the compiler to
move for me here as it does with returns and unnamed temporaries. That
is the natural thing to do.

> And perhaps even more importantly, coding:
>
> void f(A&& a)
> {
>      g(a);  // calls g(const A&)
>      h(a);  // calls h(const A&)
>      if (false)
>      {
>          h(a); // calls h(const A&)
>      }
> }
>
> instead (if that is what the programmer intends) gives the programmer
> more options on this observable behavior.  If the compiler goes into
> "automatic transmission" mode and starts implicitly moving when it can
> be observed, the result is sometimes going to be an extremely irritated
> client.

Again, I'm not convinced about the observability part. But even so, the
programmer will very seldom want to pass a reference to a variable as if
it were const, just to then let it disappear right after that. That's
the "bad" behavior of today we want to replace!

If the programmer wanna and wanna the unnatural thing every once in a
million leap years, it is allowed with extra syntax:

void f(A&& a)
{
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (false)
      {
          h(const_cast<A&>(a)); // calls h(const A&)
      }
}

>>The current solution is to
>>prescribe a cast that manually realizes the notion of last access:
>>
>>void forwarder(const A& a) {
>>     g(a);
>>}
>>
>>void forwarder(A&& a) {
>>     g(static_cast<A&&>(a));
>>}
>
>
> I'd prefer the more descriptive:
>
> void forwarder(A&& a) {
>      g(std::forward<A>(a));
> }

[Discreetely pocketing a few grenades, a machine gun, and the weapon
number 7 in Doom.] Here's where we flat out disagree. I want to fetch a
dude and forward it elswehere. There's absolutely no way anyone can
defense that I need to do more than

void forward(type name)
{
     g(name);
}

in order to forward! Why in the world would I have to do it differently
for some certain type? Where's the "perfect" in "perfect forwarding"? I
think the only defensible position is that, for the sake of simplicity,
we humbly ask programmers to use some awkward syntax for forwarding.
Then we can discuss how much we want to simplify things.

> Personally I think the explicitness of this earns bonus points (e.g. you
> can search for "forward"), especially in the light that A&& doesn't
> automatically mean forward.  For example:
>
> struct A {};
>
> std::ostream&
> operator<< (std::ostream&& os, const A&)
> {
>     return os << "A";
> }
>
> Here "&&" is used for the ostream even though no moving nor forwarding
> is intended.  The reason for the "&&" is simply so clients can use
> rvalue streams:
>
> A a;
> ..
> std::ofstream("filename") << a;

I understand that. But the underlying concept is the same - rvalue
references mean fundamentally that there is a reference that won't be
accessed beyond its current scope. That's what imho the proposal should
aim at formalizing consistently.

> I think all of them will end up requiring IPA unless we want to expose
> surprising (observable) behavior in some cases.  And I really don't
> think we want to require IPA in the standard (although I'm a big fan of
> IPA).  And I really don't want to see observable behavior change
> depending on whether or not IPA was done, and to what quality it was
> done.

Ok what the heck is IPA? Oh, "intra-procedural analysis"? It's already
done: any compiler that has always issued "warning: not all paths return
  a value" has done it. At the backend level, they all do liveness
analysis for register allocation. The only novelty element would be the
presence of a *backward* analysis in the front-end (code reachability is
a forward analysis). But wait. Many compilers (such as gcc) do backward
analysis to optimize stack frame computation: in gcc, given the code:

void foo()
{
     while (cond1)
     {
         if (cond2)
         {
             int x;
         }
         else
         {
             int y;
         }
     }
     if (cond3)
     {
         int z;
     }
}

x, y, and z are allocated at the same address on the stack (thus
minimizing the stack frame size and improving locality) because it can
demonstrate that they aren't alive at the same time.

So... we're way closer than we might think. And I think things like
incorporating such analyses is the way towards modern compiling
techniques. We've spent enough time on parsing.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 19 Jul 2005 05:07:14 GMT
Raw View
Howard Hinnant wrote:
> In article <IJuFKo.H75@beaver.cs.washington.edu>,
>  SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
>  Website For Email)") wrote:
>
>
>>Addendum:
>>
>>I also realize that the notion of binding the last reference of a named
>>object to an rvalue also applies to the case when the name is an actual
>>lvalue. Consider:
>>
>>struct A {};
>>
>>void h(A&);
>>void h(A&&);
>>
>>void g(A&);
>>void g(A&&);
>>
>>void f()
>>{
>>     A a; // lvalue
>>     g(a);  // calls g(A&)
>>     h(a);  // calls h(A&&)
>>}
>>
>>The same three options apply to this case.
>
>
> This particular scenario is more interesting and quite similar to the
> already proposed implicit move on return:
>
> A f()
> {
>      A a; // lvalue
>      g(a);  // calls g(A&)
>      return a;  // as if return std::move(a)
> }
>
> The implicit cast to rvalue is only allowed for those cases where RVO is
> already allowed: i.e. when the variable has non-cv-qualified automatic
> storage duration and the return type is the same type as the variable
> returned.

Great. The point I am trying to push is that returning an automatic
variable is only a simple particular case of the general concept behind
moving, which is: the last access to a variable is moveable.

> However in your case:
>
> void f()
> {
>      A a; // lvalue
>      g(a);  // calls g(A&)
>      h(a);  // calls h(A&&)
> }
>
> What if only h(A&) exists?  Does this change then not break existing
> code?  I suppose the compiler could back off of the implicit cast to
> rvalue if only h(A&) exists (indeed this is what is currently proposed
> for the return statement in N1770).

If only h(A&) exists, then it will be invoked. There's no breaking of
existing code indeed.

Again, *always* copying is a conservative action than moving and as such
is will be used when moving can't be used. Actually let me state the
fundamental principles that I believe govern N1377:

1. const reference <= rvalue reference <= lvalue reference, where "<="
means "is more conservative than".

2. The compiler tries to bind using the least conservative kind of
reference, within constraints.

3. The provably last access to a modifiable lvalue is always bindable to
an rvalue reference.

The "expr;" where expr creates a temporary, and the "return x;" are only
two low-hanging fruits. Attempting to fixate on these particular cases
will miss many other important cases. This often happens in language
design - it's best to address the general issue, otherwise corner cases
are gonna kill ya.

In fact, let me prove what I just said by showing an important oversight
of the proposal: moving during throw. Consider:

struct MyException
{
   string superLongMessage_;
   MyException(const MyException& rhs)
     : superLongMessage_(rhs.superLongMessage_)
   {}
   MyException(MyException&& rhs)
     : superLongMessage_(static_cast<string&&>(rhs.superLongMessage_))
   {}
   ...
};

(I will make a parenthesis right here because there's a high ball around
here that I got to dunk. In an ideal world, the compiler would have a
consistent rule that propagates the rvalue nature of an object to its
members and not force the programmer to insert casts. IOW, the code
shold be:

struct MyException
{
   string superLongMessage_;
   MyException(const MyException& rhs)
     : superLongMessage_(rhs.superLongMessage_)
   {}
   MyException(MyException&& rhs)
     : superLongMessage_(rhs.superLongMessage_)
   {}
   ...
};

with the semantics that the source string is moved to the destination
string. This is one of the many good consequences of the unified view
that my suggestion is aiming to foster.)

Back to exceptions. Well, naturally one would want to take advantage of
the move semantics when throwing and propagating exceptions. But, unless
I failed to read through properly, the proposal simply forgets to enable
move semantics for throw expressions. That means the following code will
use copying and not moving:

void foo()
{
   if (something)
   {
      MyException e;
      e.setMsg("This string will be copied... sorry!");
      throw e;
   }
   ...
}

This is a very sensitive point because exceptions occur when already the
system might be resource-cramped, and as such the last thing we need is
to unnecessarily duplicate resources. That is, by the way, why the C++
Coding Standards book suggests using mutable members and move semantics
in exception classes if copying fails.

But wait, there's less. When propagating the exception, the current
standard says that the copy constructor will be called an
implementation-defined number of times. So if I just want to do:

void bar()
{
   try
   {
     foo();
   }
   catch (MyException& e)
   {
     cerr << "And here we go with some more copying...\n";
     throw; // could copy some more!
   }
}

Because the proposal doesn't state exactly under which condition a value
can be bound to an rvalue reference, it's hard to clarify whether
propagation during throw will be made via rvalue references. Here's a quote:

-------------------
rvalues will prefer rvalue references. lvalues will prefer lvalue
references. CV qualification conversions are considered secondary
relative to r/l-value conversions. rvalues can still bind to a const
lvalue reference (const A&), but only if there is not a more attractive
rvalue reference in the overload set. lvalues can bind to an rvalue
reference, but will prefer an lvalue reference if it exists in the
overload set. The rule that a more cv-qualified object can not bind to a
less cv-qualified reference stands ... both for lvalue and rvalue
references.
-------------------

Later it says:

-------------------
     Named rvalue references are treated as lvalues.
-------------------

And later it says:

-------------------
     Unnamed rvalue references are treated as rvalues.
-------------------

And further later it says:

-------------------
A further language refinement can be made at this point. When returning
a non-cv-qualified object with automatic storage from a function, there
should be an implicit cast to rvalue
-------------------

What I see here is a lot of pointing at the trees and a failure to show
the forest. When this is applied to my example with exceptions, "throw
x;" is guaranteed to issue a full-blown copy, and the subsequent
propagation is tricky because it's unclear whether the exception being
propagated is an rvalue.

Howard, simplicity is high on your list of desirable traits. It is
likely that you will be glad to find ways to simplifying the proposal
while at the same time making it more powerful.

I don't have an entirely formed idea of a precise wording, but I think
the basics are:

1. An unnamed value can be bound to an rvalue reference

2. The last reference of a non-const named value within its scope can be
bound to an rvalue reference

3. For a rvalue reference, the non-static member variables are
considered named rvalues and rules (1) and (2) apply recursively.

Points 1 and 2 are obvious and cover the existing proposal. In
particular, they cover returns and throws as trivial particular cases of
a more comprehensive principle.

Let me clarify point 3. Consider:

struct B
{
   ...
};

struct A
{
   B b_;
   ...
};

void g(const B&);
void g(B&&);

void f()
{
   A a;
   g(a.b_); // calls g(const B&);
   g(a.b_); // calls g(B&&);
   g(A().b_); // calls g(B&&);
}

So basically the rule number 3 nicely propagates the rules to members.
That way the safe optimization opportunities are extended, and there are
fewer casts needed.

What do you think?


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Tue, 19 Jul 2005 05:09:35 GMT
Raw View
In article <IJuJwL.yMA@beaver.cs.washington.edu>,
 SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See
 Website For Email)") wrote:

> > In the example f(A&& a) above, what if f() isn't overloaded?  That means
> > that lvalues can bind to f(A&&).  This further means that whether the
> > last h(a) calls h(A&&) or h(const A&) is visible to the client.  Thus
> > this suggestion is no longer a pure optimization, but a change in
> > observable behavior.  Or the interface of f() must document that if the
> > condition is true, "a" is moved from, else it isn't.
>
> I'm not sure I understand. Could you post an example? The "above" code
> that I'm seeing is:
>
> void f(A&& a)
> {
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (false)
>       {
>           h(a); // calls h(A&&)
>       }
> }
>
> So... where's the change in observable behavior?

Here's a complete program that will either assert or not, depending upon
the application of the N1377 rules, or your proposed modification to
those rules.

#include <utility>
#include <cassert>

struct A
{
    A() : data_(1) {}
    A(A&& a) : data_(a.data_) {a.data_ = 0;}
    int data_;
};

void g(const A&) {}
void g(A&& a) {a.data_ = 0;}

void h(const A&) {}
void h(A&& a) {a.data_ = 0;}

void f(A&& a)
{
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (true)
      {
          h(a); // calls h(A&&) (by Alexandrescu rules)
      }
}

int main()
{
    A a;
    f(a);
    assert(a.data_ == 1);
}

By the N1377 rules, it does not assert.  If we change rules as you
propose, and thus the second h(a) calls h(A&&), then the assert fires:
Observable behavior.  And nobody has explicitly requested a move or a
forward.  What if the author of f() never intended to move from 'a'?
With your modification how would he accomplish that?  The only explicit
request is that f() accepts both rvalue and lvalue (non-const) A's.  And
there are legitimate reasons other than moving and forwarding for
wanting to accept such arguments.

Contrast that with this:

void f()
{
      A a;
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (true)
      {
          h(a); // calls h(A&&) (by Alexandrescu rules)
      }
}

The only way for clients to observe whether the second h(a) calls h(A&&)
or h(const A&) is by counting side effects within A(const A&) or A(A&&).
And counting side effects within A(const A&) is already undefined
behavior in C++98.  So a natural extension is to continue this tradition
with A(A&&).  So this part of your suggestion looks safe to me (assuming
the compiler can prove last reference).  However I wonder about
something like:

void f()
{
      A a;
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (condition)
          h(a); // calls h(A&&) or h(const A&) ?
      a = q();
}

The immediate application is std::swap:

template <class T>
inline
void
swap(T& a, T& b)
{
    T tmp(std::move(a));
    a = std::move(b);
    b = std::move(tmp);
}

How many explicit calls to std::move are required here?  Is it actually
less confusing code to eliminate one or more of them?

> I will skip for now the discussion referring to this point coz I can't
> understand it.

If I haven't cleared up my concerns, please elaborate on our
miscommunication as I believe this point is key to further productive
discussions.

> By the same token it could be argued that with move semantics in C++98
> people could be asked to insert std::move where appropriate without any
> change in the core language. You know very well there is burden. There
> is burden here, too. I don't want to insert move. I want the compiler to
> move for me here as it does with returns and unnamed temporaries. That
> is the natural thing to do.

The big burden in C++98 is not moving from rvalues, but in building
classes that have different behavior when "copying" from rvalues vs from
lvalues.  auto_ptr is the example.  auto_ptr_ref is a mess and actually
broken from the original intent (with respect to seamless cooperation
between auto_ptr<Derived> and auto_ptr<Base>).  It can be fixed (I
think) in C++98, but not easily.  And there are many, many classes that
could benefit from pilfering from rvalues during a "copy" operation.
Asking each class author to reinvent the internal mechanics of auto_ptr
(or a fixed version of auto_ptr) is not reasonable.

By contrast, inserting std::move(t) when you want to pretend that 't' is
an rvalue is quite reasonable.  That being said, if the language can
assure that an implicitly inserted std::move(t) is not observable in
some context, I'm all for it, and is the fundamental rationale for:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Moving%
20from%20local%20values

> If the programmer wanna and wanna the unnatural thing every once in a
> million leap years, it is allowed with extra syntax:
>
> void f(A&& a)
> {
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (false)
>       {
>           h(const_cast<A&>(a)); // calls h(const A&)
>       }
> }

Imho the "default behavior" here should be: no observable effect.  A
simple addition to f(), such as a goto, could radically change its
behavior:

void f(A&& a)
{
start_over:
      g(a);  // calls g(const A&)
      h(a);  // calls h(const A&)
      if (condition)
      {
          h(a); // behavior changes due to maintenance
          ... lots of code here
          goto start_over;  // added during maintenance
      }
}

Such radical changes in behavior could make maintenance a nightmare.

> [Discreetely pocketing a few grenades, a machine gun, and the weapon
> number 7 in Doom.] Here's where we flat out disagree. I want to fetch a
> dude and forward it elswehere. There's absolutely no way anyone can
> defense that I need to do more than
>
> void forward(type name)
> {
>      g(name);
> }
>
> in order to forward! Why in the world would I have to do it differently
> for some certain type? Where's the "perfect" in "perfect forwarding"? I
> think the only defensible position is that, for the sake of simplicity,
> we humbly ask programmers to use some awkward syntax for forwarding.
> Then we can discuss how much we want to simplify things.

But the only clue to your intent here is the name of the function
"forward".  Are there not plenty of functions that have this structure,
but not the semantics of "forward"?

Perhaps only those functions taking A&& parameters should use forward
semantics.  But that leads us back to the problems already described:
There are times you want to use a && parameter, and don't want to
forward (random_shuffle, I/O, shared_ptr for just three).

> Ok what the heck is IPA? Oh, "intra-procedural analysis"?

Well, since it is after hours here in the Ithaca office, IPA stands for
India Pale Ale. :-)  But during office hours it is InterProcedural
Analysis.

>It's already
> done: any compiler that has always issued "warning: not all paths return
>   a value" has done it. At the backend level, they all do liveness
> analysis for register allocation. The only novelty element would be the
> presence of a *backward* analysis in the front-end (code reachability is
> a forward analysis). But wait. Many compilers (such as gcc) do backward
> analysis to optimize stack frame computation: in gcc, given the code:
>
> void foo()
> {
>      while (cond1)
>      {
>          if (cond2)
>          {
>              int x;
>          }
>          else
>          {
>              int y;
>          }
>      }
>      if (cond3)
>      {
>          int z;
>      }
> }
>
> x, y, and z are allocated at the same address on the stack (thus
> minimizing the stack frame size and improving locality) because it can
> demonstrate that they aren't alive at the same time.
>
> So... we're way closer than we might think. And I think things like
> incorporating such analyses is the way towards modern compiling
> techniques. We've spent enough time on parsing.

Yes, IPA has been done.  I've personally been working with it for over a
year now.  It is really good stuff.  It can be done with respect to
different functions within a translation unit, or even with respect to
different translation units.

When you have a non-overloaded function like:

f(A&& a);

You have no way of knowing if 'a' truly refers to an rvalue without IPA
(across translation units).  If 'a' refers to an lvalue (in any TU),
then you have observable behavior if you modify 'a'.  And that makes me
nervous when f(A&& a) might or might not modify 'a' based on a static
flow analysis combined with an actual dynamic flow path.  It could
probably be made to work, it just seems very error prone/fragile to me.

void f(A&& a)
{
..
    h(a);  // modifies a?
..
}

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: SeeWebsiteForEmail@moderncppdesign.com ("Andrei Alexandrescu (See Website For Email)")
Date: Tue, 19 Jul 2005 16:07:27 GMT
Raw View
Howard Hinnant wrote:
> Here's a complete program that will either assert or not, depending upon
> the application of the N1377 rules, or your proposed modification to
> those rules.
>
> #include <utility>
> #include <cassert>
>
> struct A
> {
>     A() : data_(1) {}
>     A(A&& a) : data_(a.data_) {a.data_ = 0;}
>     int data_;
> };
>
> void g(const A&) {}
> void g(A&& a) {a.data_ = 0;}
>
> void h(const A&) {}
> void h(A&& a) {a.data_ = 0;}
>
> void f(A&& a)
> {
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (true)
>       {
>           h(a); // calls h(A&&) (by Alexandrescu rules)
>       }
> }
>
> int main()
> {
>     A a;
>     f(a);
>     assert(a.data_ == 1);
> }

That's a misunderstanding. To prove that the assert is happening, you
must access the value. By my rules the lvalue, if you do access it,
won't be bound to a rvalue reference. It's like in quantum mechanics: to
measure it, you gotta touch it. If you touch it, you disturb it and you
can't measure it.

> By the N1377 rules, it does not assert.  If we change rules as you
> propose, and thus the second h(a) calls h(A&&), then the assert fires:
> Observable behavior.

Not the case, see above

> And nobody has explicitly requested a move or a
> forward.  What if the author of f() never intended to move from 'a'?
> With your modification how would he accomplish that?

If they really wanted (and again, it's very rare to care about a value
that will be destroyed immediately) they can always add const to it.

> The only explicit
> request is that f() accepts both rvalue and lvalue (non-const) A's.  And
> there are legitimate reasons other than moving and forwarding for
> wanting to accept such arguments.
>
> Contrast that with this:
>
> void f()
> {
>       A a;
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (true)
>       {
>           h(a); // calls h(A&&) (by Alexandrescu rules)
>       }
> }
>
> The only way for clients to observe whether the second h(a) calls h(A&&)
> or h(const A&) is by counting side effects within A(const A&) or A(A&&).
> And counting side effects within A(const A&) is already undefined
> behavior in C++98.  So a natural extension is to continue this tradition
> with A(A&&).  So this part of your suggestion looks safe to me (assuming
> the compiler can prove last reference).

Oui.

> However I wonder about
> something like:
>
> void f()
> {
>       A a;
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (condition)
>           h(a); // calls h(A&&) or h(const A&) ?
>       a = q();
> }

That's a good question. Again, what I'd want is us to work together
towards a perhaps better proposal, not me defending a view that's not
entirely formed yet.

The assignment is really a write, but the operator= code can read *this.
  To be conservative, we might consider any use of the variable (be it
in writing too) an "access". By that rule, h(a) will bind to h(const A&).

> The immediate application is std::swap:
>
> template <class T>
> inline
> void
> swap(T& a, T& b)
> {
>     T tmp(std::move(a));
>     a = std::move(b);
>     b = std::move(tmp);
> }
>
> How many explicit calls to std::move are required here?  Is it actually
> less confusing code to eliminate one or more of them?

It definitely is. By my rules the function can be implemented as the
above, or as:

template <class T>
inline
void
swap(T& a, T& b)
{
     T tmp(std::move(a));
     a = std::move(b);
     b = tmp;
}

The reader can easily understand that the explicit moves are needed
because they come from some references to objects coming from the
outside, while tmp is a different thing - it's a local variable that
will disappear so it can be moved.

> If I haven't cleared up my concerns, please elaborate on our
> miscommunication as I believe this point is key to further productive
> discussions.

Great. I'd say we're doing good so far. Did you see my other post with
the member variable access? I think it's cool!

> The big burden in C++98 is not moving from rvalues, but in building
> classes that have different behavior when "copying" from rvalues vs from
> lvalues.  auto_ptr is the example.  auto_ptr_ref is a mess and actually
> broken from the original intent (with respect to seamless cooperation
> between auto_ptr<Derived> and auto_ptr<Base>).  It can be fixed (I
> think) in C++98, but not easily.  And there are many, many classes that
> could benefit from pilfering from rvalues during a "copy" operation.
> Asking each class author to reinvent the internal mechanics of auto_ptr
> (or a fixed version of auto_ptr) is not reasonable.

I agree.

> By contrast, inserting std::move(t) when you want to pretend that 't' is
> an rvalue is quite reasonable.  That being said, if the language can
> assure that an implicitly inserted std::move(t) is not observable in
> some context, I'm all for it, and is the fundamental rationale for:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Moving%
> 20from%20local%20values

Great. So the discussion now is focused on how often we should require
the compiler to insert std::move. I'd say, the more required, the
better. User code will forget to insert std::move so things will compile
and run, just suboptimally. Is that what we want?

>>If the programmer wanna and wanna the unnatural thing every once in a
>>million leap years, it is allowed with extra syntax:
>>
>>void f(A&& a)
>>{
>>      g(a);  // calls g(const A&)
>>      h(a);  // calls h(const A&)
>>      if (false)
>>      {
>>          h(const_cast<A&>(a)); // calls h(const A&)
>>      }
>>}
>
>
> Imho the "default behavior" here should be: no observable effect.  A
> simple addition to f(), such as a goto, could radically change its
> behavior:
>
> void f(A&& a)
> {
> start_over:
>       g(a);  // calls g(const A&)
>       h(a);  // calls h(const A&)
>       if (condition)
>       {
>           h(a); // behavior changes due to maintenance
>           ... lots of code here
>           goto start_over;  // added during maintenance
>       }
> }
>
> Such radical changes in behavior could make maintenance a nightmare.

And the point is? The first code has a const_cast, the latter doesn't...
not sure I understand. Anyway, the latter example will bind to the const
reference because h(a) is not the last static use of a anymore. Is it a
bad thing that the compiler executes more conservative code in some
cases, and more optimal code in others???

> Perhaps only those functions taking A&& parameters should use forward
> semantics.  But that leads us back to the problems already described:
> There are times you want to use a && parameter, and don't want to
> forward (random_shuffle, I/O, shared_ptr for just three).

template <class It>
void random_shuffle(It b, It e);

I dun understand. Let's focus on this function (or any other you choose,
just describe it) and see how my proposal could break things.

> Well, since it is after hours here in the Ithaca office, IPA stands for
> India Pale Ale. :-)  But during office hours it is InterProcedural
> Analysis.

Whoa whoa. We only need INTRAprocedural analysis. Maybe that tidbit of
information (plus the India Pale Ale) would lighten the mood? :o) Intra
is way easier than inter.

> Yes, IPA has been done.  I've personally been working with it for over a
> year now.  It is really good stuff.  It can be done with respect to
> different functions within a translation unit, or even with respect to
> different translation units.

Again: I = INTRA. Easy. Simple. Cheap.

> When you have a non-overloaded function like:
>
> f(A&& a);
>
> You have no way of knowing if 'a' truly refers to an rvalue without IPA
> (across translation units).  If 'a' refers to an lvalue (in any TU),
> then you have observable behavior if you modify 'a'.  And that makes me
> nervous when f(A&& a) might or might not modify 'a' based on a static
> flow analysis combined with an actual dynamic flow path.  It could
> probably be made to work, it just seems very error prone/fragile to me.

Ok there does seem to be a deep misunderstanding here. My proposal is
miles away from what you describe above. I'm sleepy now, but I took a
loong time to write another post better describing what I am suggesting.
I think you'll like it because it doesn't add any headache - it reduces
them.


Andrei

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Tue, 19 Jul 2005 16:10:37 GMT
Raw View
Andrei's-unscavengeable-email-alias ("Andrei Alexandrescu (See Website For Email)") writes:

> Howard Hinnant wrote:
>> Imho this makes the rules too complicated for programmers reading
>> the code.  It is important for the human reader of the code to know
>> if something is treated as an rvalue or not.  There are more reasons
>> to use && than just moving and forwarding.  And trying to figure out
>> if your compiler has just used your named A&& as an lvalue or rvalue
>> should not require the programmer to quickly do a visual flow
>> analysis on the fly.
>
> It is true that there is more complication. However (1) if lvalue
> references are just an more conservative version of rvalue references,
> then conversely rvalue references are only an optimization of lvalue
> references

That equation only holds for unnamed rvalue references.  If you move
from an unnamed object it's just an optimization.  If you move from a
named object it's information-destroying.

> so whether the compiler chooses one or the other is not of high
> relevance. After all, nobody blinks an eye today when the compiler
> discretionary eliminates calls to copy constructors because it
> assumes what they do.

Moving is a much more observable change.  More significantly, the
conditions under which copy elision is done were very carefully chosen
*so that* nobody would blink an eye.  You can't just do it
willy-nilly.  I still remember how they were still fixing the rules at
the Morristown meeting (the one where the standard was ratified).

>> But coding the following instead:
>> void f(A&& a)
>> {
>>      g(a);  // calls g(const A&)
>>      h(a);  // calls h(const A&)
>>      if (false)
>>      {
>>          h(std::move(a)); // calls h(A&&)
>>      }
>> }
>> is such a small burden on the author of f() (if that is what is
>> intended) that the extra complication implied by your suggestion
>> does not seem worth it.
>
> By the same token it could be argued that with move semantics in C++98
> people could be asked to insert std::move where appropriate without any
> change in the core language. You know very well there is burden. There
> is burden here, too. I don't want to insert move. I want the compiler to
> move for me here as it does with returns and unnamed temporaries. That
> is the natural thing to do.

Well, let's make this example realistic first.  Are you planning to
write lots of non-template functions taking T&& parameters?  The
picture of what f can do un-observably changes drastically when you
precede it with "template <class A>."

>>> The current solution is to prescribe a cast that manually realizes
>>> the notion of last access:
>>>
>>>void forwarder(const A& a) {
>>>     g(a);
>>>}
>>>
>>>void forwarder(A&& a) {
>>>     g(static_cast<A&&>(a));
>>>}
>> I'd prefer the more descriptive:
>> void forwarder(A&& a) {
>>      g(std::forward<A>(a));
>> }
>
> [Discreetely pocketing a few grenades, a machine gun, and the weapon
> number 7 in Doom.] Here's where we flat out disagree. I want to fetch a
> dude and forward it elswehere. There's absolutely no way anyone can
> defense that I need to do more than
>
> void forward(type name)
> {
>     g(name);
> }
>
> in order to forward! Why in the world would I have to do it
> differently for some certain type? Where's the "perfect" in "perfect
> forwarding"?

The "perfect" is in preserving information about what is an rvalue.

>> Personally I think the explicitness of this earns bonus points
>> (e.g. you can search for "forward"), especially in the light that
>> A&& doesn't automatically mean forward.  For example:
>> struct A {};
>> std::ostream&
>> operator<< (std::ostream&& os, const A&)
>> {
>>     return os << "A";
>> }
>> Here "&&" is used for the ostream even though no moving nor
>> forwarding is intended.  The reason for the "&&" is simply so
>> clients can use rvalue streams:
>> A a;
>> ..
>> std::ofstream("filename") << a;
>
> I understand that. But the underlying concept is the same - rvalue
> references mean fundamentally that there is a reference that won't be
> accessed beyond its current scope.

No, that's what a reference bound to an rvalue means.  An rvalue
reference means a reference that *can* be bound to an rvalue.  An
rvalue is an object that won't be accessed beyond its current scope.

> That's what imho the proposal should aim at formalizing
> consistently.

Well, you could try to write a proposal around the idea that rvalue
references mean fundamentally that there is a reference that won't be
accessed beyond its current scope, and I invite you to be my guest!
You'd have to start from the ground up and rethink everything, because
it's a major departure from the foundation of our proposal.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]