Topic: C++11 Rvalue-references


Author: brangdon@cix.compulink.co.uk (Dave Harris)
Date: Fri, 29 Jun 2012 12:15:12 -0700 (PDT)
Raw View
wkaras@yahoo.com (W Karas) wrote (abridged):
> I don't see how this issue is any different from an exception being
> thrown in a destructor.  You just have to handle it.  Sentries and
> catch/rethrow are the methods I'm aware of.

Best practice is to wrap each resource in an instance variable; the
instance variable's destructor will be invoked even if the main class's
destructor throws. That approach is not available here. There are ways
to deal with it, but they are messy. This goes against the theme of
C++11, which is to make it easier to get things right rather than
harder.


> > What about composite objects?
> >    this->a = static_cast<std::temp<Arg> &>(other.a);
> >    this->b = other.b;
> >
> > Does other get destructed or not?
>
> I don't directly address this but I think what I wrote clearly
> implies it does get destructed.

So in effect, it is deleted twice. As this is undefined behaviour, it
can't be allowed, so your proposal is unable to handle a case that
std::move() handles naturally, and you have lost some opportunities
for optimisation. Rvalue references are more uniform and composeable.


> I don't see where the paper mentions anything similar to
> what I have proposed.

Your proposal is a form of "destructive move semantics".


> My approach allows for more optimization.

It seems to me it is less general, that is, it can be used in fewer
situations (because it cannot be applied to parts of objects). I
don't see how it would help improve a block-copy operation in
std::vector, for example, because there we are moving elements without
destroying them.

In the situations where your approach can be used, rvalue references
allow the big O(n) optimisations. You can move from a std::vector in
constant time, for example. As far as I can tell, the benefit of your
proposal is that it also avoids setting the source pointers to null,
which seems to me to be a very minor additional thing, and not worth
the loss of other opportunities and the error-proneness.


> I can't think of a quick example, but I would think that it is a
> natural thing to do with templates, to create a "synonym type" for
> an existing type to trigger different treatment of objects without
> changing the intrinsic behavior of the object.

Sure. For a pure library feature that makes sense. Since for this we
need support from the compiler anyway, we might as well use something
more succinct.


>  To create a third type of pointer syntax to get different treatment
> of objects just seems icky somehow.

It surprised me initially. But I now understand that it reflects a
deeper view of lvalues and rvalues. They get different syntax because
they are different. This leads to other benefits, such as perfect
forwarding.

-- Dave Harris, Nottingham, UK.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: Jason McKesson <jmckesson@gmail.com>
Date: Fri, 29 Jun 2012 12:16:08 -0700 (PDT)
Raw View
> My approach allows for more optimization.  This is dangerous, like
> casting, so it would be consistent to make it verbose like casting.
> The use of template-like syntax also follows the casting precedent.

Cast operators look like template function calls. They behave like
function calls. And some of them effectively are function calls
(dynamic_cast). Your `temp<>` syntax looks like a type. Except that it
isn't a type; it doesn't behave like a type.

The other thing is that you *don't* want movement to be that
dangerous. Casting is verbose because it's something you theoretically
shouldn't need to be doing. Movement is something that you *want* to
do; making it verbose simply makes people want to use alternatives
like swap and such.

By making movement less inherently dangerous, it becomes something you
can do implicitly when you know it won't hurt anything. Values
returned from functions can be captured and moved just fine. No need
for explicit user intervention or anything of the sort. And so forth.

What you're doing isn't movement; it's generalized *elision*.

> I can't think of a quick example, but I would think that it is a
> natural thing to do with templates, to create a "synonym type" for an
> existing type to trigger different treatment of objects without
> changing the intrinsic behavior of the object.  So the
> "pseudo-class-template" seems natural.  A reference allows a pointer
> id for an object to be used with the same syntax as the object's name.
>  To create a third type of pointer syntax to get different treatment
> of objects just seems icky somehow.

It's a lot easier to understand than this
template-type-that-isn't-a-template-or-a-type. First, references
aren't "pointer syntax"; they're references. More importantly, the
only functional difference between an l-value reference and an r-value
reference is what they expressions they will bind to and overload
resolution.

The principle complexity with r-value references isn't in the
reference type. It's the 3 separate expression types (lvalue, xvalue,
prvalue) and the two groupings of them (glvalue and rvalue). However,
even that complexity isn't necessary to understand how to use
movement. That just requires the simple and easily understood
`std::move`, as well as how to write move constructors/assignment
operators. Though even the latter should only rarely be needed.

Complex on the inside, simple on the outside. Much better than your
system which is complex for everyone and very verbose.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: W Karas <wkaras@yahoo.com>
Date: Sun, 17 Jun 2012 18:26:39 -0700 (PDT)
Raw View
Just seems like there are simpler and better ways to get the same
capability.  For example, add the "pseudo-type-template" std::temp<T>
(T may not have a const or volatile modifier).  A reference/pointer to
temp<T> implicitly converts to a pointer/reference to T, and this
conversion is the only valid operation on temp<T> that is directly
usable.  A pointer/reference to T explicitly converts, using
static_cast, to a pointer/reference to temp<T> .  All
compiler-generated nameless temporaries are of type temp<T>.  When the
following 3 conditions are met:

1.  An instance of temp<T> is the actual parameter to a function call.
 (This is considered to include the case where a member function of T
is called on the temp<T> instance.)
2.  Nominally the compiler would generate a call to T::~T on the
instance immediately after the function call.

3.  The overload of the function that is the best match to the call
has a formal parameter of type temp<T> & for the actual parameter of
type temp<T>.

then the compiler will omit the generated call to T::~T , and instead
just deallocate the memory space for the temp<T> instance.  If
conditions 1 and 3 but not 2 are met, the compiler should reject the
nominally best-matching overload.  This would cause compilation to
fail if there were no other matching overloads.

To enable explicit optimizations, if t is the name of an instance of
T, and the expression static_cast<temp<T> &>(t) appears as a
sub-expression in an expression that is an actual parameter to a
function call in the block where t was constructed, the compiler will
suppress generation of destructor calls for t, after said function
call.  The compiler will instead just deallocate the storage for t.

To enable explicit optimizations involving dynamically allocated
objects, the Standard would require that it will always be possible to
substitute this:

pt->~T();
delete static_cast<void *>(pt);

for:

delete pt;

or:

for (unsigned i = 0; i < N_elems_t; ++i)
 pt[i].~T();
delete static_cast<void *>(pt);

for:

delete [] pt;


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: brangdon@cix.compulink.co.uk (Dave Harris)
Date: Sun, 24 Jun 2012 23:49:57 -0700 (PDT)
Raw View
wkaras@yahoo.com (W Karas) wrote (abridged):
> Just seems like there are simpler and better ways to get the same
> capability.  For example, add the "pseudo-type-template"
> std::temp<T>

I'm not really qualified to answer this, but no-one else has, so I'll
try. Perhaps my poor answer will provoke a better one.

As far as I can tell, the main semantic difference is that with your
proposal, objects are not always destroyed. This seems error prone.
For example:

   void MyClass::demo( std::temp<Arg> &arg ) {
       this->p = new int;
       this->arg = arg;
   }

If the new expression throws an exception, should the compiler delete
the argument or not? At the point of call, the compiler doesn't know
whether arg is moved before or after the throw.

What about composite objects?
   this->a = static_cast<std::temp<Arg> &>(other.a);
   this->b = other.b;

Does other get destructed or not?

The C++11 rule, that objects are always destroyed, actually seems
simpler to me. It is easier to reason with. It's more obvious what
will happen in more situations. Your approach was considered but
abandoned quite early; for example, it is discussed and rejected
in this 2002 paper by Hinnant, Dimov and Abrahams:
 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm

Look for "Alternative move designs" and "Destructive move semantics".

>From a syntactic point of view, what you suggest seems more verbose.
I also don't see any benefit from dressing the new rules in old
clothes.

-- Dave Harris, Nottingham, UK.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: W Karas <wkaras@yahoo.com>
Date: Thu, 28 Jun 2012 13:23:13 -0700 (PDT)
Raw View
On Monday, June 25, 2012 2:49:57 AM UTC-4, Dave Harris wrote:
...
>
> As far as I can tell, the main semantic difference is that with your
> proposal, objects are not always destroyed. This seems error prone.
> For example:
>
>    void MyClass::demo( std::temp<Arg> &arg ) {
>        this->p = new int;
>        this->arg = arg;
>    }

I don't see how this issue is any different from an exception being
thrown in a destructor.  You just have to handle it.  Sentries and
catch/rethrow are the methods I'm aware of.

>
> If the new expression throws an exception, should the compiler delete
> the argument or not? At the point of call, the compiler doesn't know
> whether arg is moved before or after the throw.

No it must not, the function must still be equivalent to destruction,
even if an exception occurs.

>
> What about composite objects?
>    this->a = static_cast<std::temp<Arg> &>(other.a);
>    this->b = other.b;
>
> Does other get destructed or not?

I don't directly address this but I think what I wrote clearly implies
it does get destructed.  The compiler would not insert at call to
this->a anyway, so the cast is moot.

>
> The C++11 rule, that objects are always destroyed, actually seems
> simpler to me. It is easier to reason with. It's more obvious what
> will happen in more situations. Your approach was considered but
> abandoned quite early; for example, it is discussed and rejected
> in this 2002 paper by Hinnant, Dimov and Abrahams:
>  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
>
> Look for "Alternative move designs" and "Destructive move semantics".

Exceptions to rules are bad unless there is good motivation for them.
(If you get too down on exceptions to rules, you'll go back to using C
:) .)  I don't see where the paper mentions anything similar to what I
have proposed.

>
> >From a syntactic point of view, what you suggest seems more verbose.
> I also don't see any benefit from dressing the new rules in old
> clothes.

My approach allows for more optimization.  This is dangerous, like
casting, so it would be consistent to make it verbose like casting.
The use of template-like syntax also follows the casting precedent.

I can't think of a quick example, but I would think that it is a
natural thing to do with templates, to create a "synonym type" for an
existing type to trigger different treatment of objects without
changing the intrinsic behavior of the object.  So the
"pseudo-class-template" seems natural.  A reference allows a pointer
id for an object to be used with the same syntax as the object's name.
 To create a third type of pointer syntax to get different treatment
of objects just seems icky somehow.

...


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]