Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C++ (http://www.velocityreviews.com/forums/f39-c.html)
-   -   Giving an rvalue ref to a function taking an rvalue ref (http://www.velocityreviews.com/forums/t951109-giving-an-rvalue-ref-to-a-function-taking-an-rvalue-ref.html)

Juha Nieminen 08-20-2012 10:29 AM

Giving an rvalue ref to a function taking an rvalue ref
 
Suppose you have a function that takes an rvalue reference as parameter,
and from within it you want to call another function that likewise takes
one, but there's also a version of that function that takes a regular
reference. (This is most often the case with copy/move constructors and
assignment operators.)

In order to call the version taking the rvalue reference, you have to
explicitly re-cast the parameter with std::move (or static_cast<type&&>()).

For example, if you were writing a move constructor in a derived class,
you would need to write it like:

MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}

Or, if you want:

MyClass(MyClass&& rhs): BaseClass(static_cast<MyClass&&>(rhs)) {}

Without the explicit cast, the normal copy constructor of the base class
would be called instead. (The same is true if you were implementing the
move assignment operator.)

But why is the explicit re-casting necessary?

Luca Risolia 08-20-2012 11:38 AM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On 20/08/2012 12:29, Juha Nieminen wrote:
> Suppose you have a function that takes an rvalue reference as parameter,
> and from within it you want to call another function that likewise takes
> one, but there's also a version of that function that takes a regular
> reference. (This is most often the case with copy/move constructors and
> assignment operators.)
>
> In order to call the version taking the rvalue reference, you have to
> explicitly re-cast the parameter with std::move (or static_cast<type&&>()).
>
> For example, if you were writing a move constructor in a derived class,
> you would need to write it like:
>
> MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}
>
> Or, if you want:
>
> MyClass(MyClass&& rhs): BaseClass(static_cast<MyClass&&>(rhs)) {}
>
> Without the explicit cast, the normal copy constructor of the base class
> would be called instead. (The same is true if you were implementing the
> move assignment operator.)
>
> But why is the explicit re-casting necessary?


Because the rvalue reference rhs is, in fact, a lvalue inside the
MyClass constructor. It is an object with a name, you can get its
address, and will be alive for the whole duration of the constructor, so
you need an explicit cast to apply move semantics again.


SG 08-20-2012 12:00 PM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On Aug 20, 12:29*pm, Juha Nieminen wrote:
> Suppose you have a function that takes an rvalue reference as parameter,
> and from within it you want to call another function that likewise takes
> one, but there's also a version of that function that takes a regular
> reference. (This is most often the case with copy/move constructors and
> assignment operators.)
>
> In order to call the version taking the rvalue reference, you have to
> explicitly re-cast the parameter with std::move (or static_cast<type&&>()).
>
> For example, if you were writing a move constructor in a derived class,
> you would need to write it like:
>
> * * MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}
>
> Or, if you want:
>
> * * MyClass(MyClass&& rhs): BaseClass(static_cast<MyClass&&>(rhs)) {}
>
> Without the explicit cast, the normal copy constructor of the base class
> would be called instead. (The same is true if you were implementing the
> move assignment operator.)
>
> But why is the explicit re-casting necessary?


It is necessary because rhs is an lvalue expression. Its declared type
only affects how you can initialize it. That's about it. You can use
this name to refer to the same object multiple times. That's the
quality of lvalues! If you have a way of accessing the same object
multiple times by a simple name this name ought to be an lvalue
expression because otherwise you would probably get accidental and
undesired mutations. It's much better and safer to be explicit about
when you lose interest in a named object (which means the use of
std::move or std::forward).

Keep in mind that the value category is not a quality of an object,
but a quality of an expression. Once you initialize a _named_
reference to refer to some object, this name is an lvalue expression
and it won't matter what its declared type is -- for safety reasons.

Cheers!
SG

s0suk3@gmail.com 08-20-2012 12:22 PM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On Monday, August 20, 2012 5:29:58 AM UTC-5, Juha Nieminen wrote:
> Suppose you have a function that takes an rvalue reference as parameter,
>
> and from within it you want to call another function that likewise takes
>
> one, but there's also a version of that function that takes a regular
>
> reference. (This is most often the case with copy/move constructors and
>
> assignment operators.)
>
>
>
> In order to call the version taking the rvalue reference, you have to
>
> explicitly re-cast the parameter with std::move (or static_cast<type&&>()).
>
>
>
> For example, if you were writing a move constructor in a derived class,
>
> you would need to write it like:
>
>
>
> MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}
>
>
>
> Or, if you want:
>
>
>
> MyClass(MyClass&& rhs): BaseClass(static_cast<MyClass&&>(rhs)) {}
>
>
>
> Without the explicit cast, the normal copy constructor of the base class
>
> would be called instead. (The same is true if you were implementing the
>
> move assignment operator.)
>
>
>
> But why is the explicit re-casting necessary?


The thing is that there's a difference between "rvalue references" and just"rvalues". Rvalue references I guess are just named rvalue references (i.e.. variables), whereas rvalues are basically things similar to temporary objects (like by-value return values). And rvalue references are actually lvalues (yes, L). The rationale for that obviously is that a named rvalue reference is no longer a temporary object that's about to be destroyed (even though it was initialized with one), so it shouldn't be (implicitly) assignable to other rvalue references.

SG 08-22-2012 03:36 PM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On 22 Aug., 17:07, Edek Pienkowski wrote:
> [...]
> point to any rationale behind that. The point is that the rules could
> have been set differently and if they are as they are there must be
> some reasoning behind it - not just how the rules are formulated.


As I said, "it makes sense". Names of objects are lvalues. The name of
an
rvalue reference is still a name you can use to refer to the same
object
multiple times. That's an lvalue quality. You DON'T want a name for an
object to be an rvalue expression -- even if it's the name of an
rvalue
reference -- because this would lead to accidental moves. So, for
safety
reasons one has to be explicit about when one loses interest in a
NAMED
object.

HTH,
SG

Luca Risolia 08-22-2012 04:10 PM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On 22/08/2012 17:07, Edek Pienkowski wrote:
> Dnia Mon, 20 Aug 2012 13:38:28 +0200, Luca Risolia napisal:
>
>> On 20/08/2012 12:29, Juha Nieminen wrote:
>>> Suppose you have a function that takes an rvalue reference as parameter,
>>> and from within it you want to call another function that likewise takes
>>> one, but there's also a version of that function that takes a regular
>>> reference. (This is most often the case with copy/move constructors and
>>> assignment operators.)
>>>
>>> In order to call the version taking the rvalue reference, you have to
>>> explicitly re-cast the parameter with std::move (or static_cast<type&&>()).
>>>
>>> For example, if you were writing a move constructor in a derived class,
>>> you would need to write it like:
>>>
>>> MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}
>>>
>>> Or, if you want:
>>>
>>> MyClass(MyClass&& rhs): BaseClass(static_cast<MyClass&&>(rhs)) {}
>>>
>>> Without the explicit cast, the normal copy constructor of the base class
>>> would be called instead. (The same is true if you were implementing the
>>> move assignment operator.)
>>>
>>> But why is the explicit re-casting necessary?

>>
>> Because the rvalue reference rhs is, in fact, a lvalue inside the
>> MyClass constructor. It is an object with a name, you can get its
>> address, and will be alive for the whole duration of the constructor, so
>> you need an explicit cast to apply move semantics again.

>
> The question was not "what is necessary" but "why". I actually often
> wondered why the explicit re-casting is necessary, and your answer is no
> answer, because it only explains the semantic rules and does not
> point to any rationale behind that. The point is that the rules could
> have been set differently and if they are as they are there must be
> some reasoning behind it - not just how the rules are formulated.
>
> You're not the only person giving this explanation when asked "why"
> to tell the truth. Who is the magnificent author of this repeated
> idiom?


The key thing to understand was that rhs is a lvalue expression in the
constructor, despite its declaration. That is what might be really
confusing for many people in my opinion. And once you know that, think
about the consequences of a - possible - implicit cast. They would be
obvious, if you knew the C++ basics better.


Garrett Hartshaw 08-22-2012 11:44 PM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On Wed, 22 Aug 2012 16:30:11 +0000 (UTC), Edek Pienkowski
<edek.pienkowski@gmail.com> wrote:
> Dnia Wed, 22 Aug 2012 18:10:38 +0200, Luca Risolia napisal:



> > On 22/08/2012 17:07, Edek Pienkowski wrote:
> >> Dnia Mon, 20 Aug 2012 13:38:28 +0200, Luca Risolia napisal:
> >>
> >>> On 20/08/2012 12:29, Juha Nieminen wrote:
> >>>> Suppose you have a function that takes an rvalue reference as

parameter,
> >>>> and from within it you want to call another function that

likewise takes
> >>>> one, but there's also a version of that function that takes a

regular
> >>>> reference. (This is most often the case with copy/move

constructors and
> >>>> assignment operators.)
> >>>>
> >>>> In order to call the version taking the rvalue reference, you

have to
> >>>> explicitly re-cast the parameter with std::move (or

static_cast<type&&>()).
> >>>>
> >>>> For example, if you were writing a move constructor in a

derived class,
> >>>> you would need to write it like:
> >>>>
> >>>> MyClass(MyClass&& rhs): BaseClass(std::move(rhs)) {}
> >>>>
> >>>> Or, if you want:
> >>>>
> >>>> MyClass(MyClass&& rhs):

BaseClass(static_cast<MyClass&&>(rhs)) {}
> >>>>
> >>>> Without the explicit cast, the normal copy constructor of the

base class
> >>>> would be called instead. (The same is true if you were

implementing the
> >>>> move assignment operator.)
> >>>>
> >>>> But why is the explicit re-casting necessary?
> >>>
> >>> Because the rvalue reference rhs is, in fact, a lvalue inside

the
> >>> MyClass constructor. It is an object with a name, you can get

its
> >>> address, and will be alive for the whole duration of the

constructor, so
> >>> you need an explicit cast to apply move semantics again.
> >>
> >> The question was not "what is necessary" but "why". I actually

often
> >> wondered why the explicit re-casting is necessary, and your

answer is no
> >> answer, because it only explains the semantic rules and does not
> >> point to any rationale behind that. The point is that the rules

could
> >> have been set differently and if they are as they are there must

be
> >> some reasoning behind it - not just how the rules are formulated.
> >>
> >> You're not the only person giving this explanation when asked

"why"
> >> to tell the truth. Who is the magnificent author of this repeated
> >> idiom?

> >
> > The key thing to understand was that rhs is a lvalue expression

in the
> > constructor, despite its declaration. That is what might be

really
> > confusing for many people in my opinion. And once you know that,

think
> > about the consequences of a - possible - implicit cast. They

would be
> > obvious, if you knew the C++ basics better.



> The rules could have been that it is not a cast - it is declared as
> rvalue-ref and only the standard says that it is named and as such

is
> an lvalue. It could have been that a cast would be necessary in
> the opposite direction - to get an lvalue from the declared (which
> means explicit) rvalue-ref. Although I agree the the _ref_ is an
> lvalue by nature more than an rvalue; my point would be that
> rvalue-ref is more of an rvalue-_ref_ than an lvalue.



> I do know C++ and hell lot more than the basics, yet I do not quite
> follow why rvalue-ref is considered an lvalue (ok, I do
> know that the standard says that if it is named then it is an
> lvalue, no need to repeat that in the thread for the fifth time;
> I do not quite get _why_). What would be the consequences of
> the opposite choice? For me it would mean less typing, I mean
> how often do you need to actually pass an rvalue-ref to
> something taking a reference or a value (cv-qualified)
> _and at the same time_ the same thing has a signature
> which takes an rvalue-ref? Any idiomatic example?



> Edek Pienkowski


a function taking a rvalue ref may need to do some calculation
with the value before passing it to another rvalue-ref taking
function.

in your scheme this would be written as:

void foo( bar && rref )
{
do_some_calculation( std::lval( rref ) );
do_more_calculations( std::lval( rref ) );
move_from_rref( rref );
}

as it is now:

void foo( bar && rref )
{
do_some_calculation( rref );
do_more_calculations( rref );
move_from_rref( std::move( rref ) );
}

In your scheme, you need to ensure that every reference to rref
before the last
uses std::lval, whereas as it is currently, you just need to ensure
that there are
no references to rref after the call to std::move, which is easier to
check.
Furthermore, if you forgot the std::lval in your scheme, then there
would
be a runtime error on the next use of rref, whereas as it is
currently, forgetting
std::move will still run as intended just not as optimally (assuming
there is a
non-rvalue move_from_rref declared).
Therefore, the rules in the standard, while resulting in more typing
for the simplest
case, are safer for other use cases.

Tobias Müller 08-23-2012 06:21 AM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
Edek Pienkowski <edek.pienkowski@gmail.com> wrote:
> Rvalue-ref is not an rvalue, that is clear. I still do not see why it
> is implicitly an lvalue and not an rvalue-ref.
>
> A good example would help.
>
> Edek Pienkowski


The important point is that you must not confuse the type of an expression
with its value category.

Every expression has a
- value, which has two properties:
- value category, one of
- lvalue
- rvalue
- type, for example:
- int
- int&
- int&&

Type and value category are essentially independent of each other.
However, there are rules, how expressions can _bind_ to types, especially
reference types:
- lvalue expressions can only bind to normal references.
- rvalue expressions can bind to both, normal and rvalue references.

Tobi

SG 08-23-2012 08:37 AM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On 22 Aug., 18:47, Edek Pienkowski wrote:
>
> Rvalue-ref is not an rvalue, that is clear.


This is not 100% correct.
A _named_ rvalue reference is not an rvalue.

> I still do not see why it
> is implicitly an lvalue and not an rvalue-ref.


Because it has a _name_. Having a _name_ allows you to refer to it
more than once. If you have the chance to refer to an object more than
once you might expect its value not to change by writing innocent-
looking code like

void push_twice(string const& what,
vector<string>& target1, vector<string>& target2)
{
target1.push_back(what);
target2.push_back(what);
}

void push_twice(string && what,
vector<string>& target1, vector<string>& target2)
{
target1.push_back(what);
target2.push_back(what);
}

If a named rvalue reference was an rvalue expression the string would
have been _implicitly_ moved into target1 even though it has a name
("what") and can be referred to multiple times. This would make
target2
contain an empty string at the end. To get the indented behaviour one
would have to write

void push_twice(string && what,
vector<string>& target1, vector<string>& target2)
{
target1.push_back(constify(what));
target2.push_back(what);
}

where "constify" would return an lvalue ref to const to prevent the
string from moving into the vector target1. This, however is much more
error prone than the actual rules. Under the actual rules you'd write

void push_twice(string && what,
vector<string>& target1, vector<string>& target2)
{
target1.push_back(what);
target2.push_back(move(what));
}

and have the "rvalueness" explicit. There is no good reason to make
named rvalue references into rvalues implicitly. Having a name is
something that screams "Lvalue". The rule is simple: All names of
objects are lvalues. And "name" doesn't have to be an identifier. It
might be any expression you can use to refer to the same object
multiple times. That's what lvalues are about. Of course, a named
rvalue reference is an lvalue, too.

Don't get confused with the name "rvalue reference". Its name only
describes that you can only _initialize_ rvalue references to refer to
objects nobody cares about because either they don't have a handle on
them or they explicitly declared that they are not interested in them
(via std::move of std::forward).

Cheers!
SG

SG 08-24-2012 09:20 AM

Re: Giving an rvalue ref to a function taking an rvalue ref
 
On 23 Aug., 11:29, Edek Pienkowski wrote:
> I won't be a bore and I won't ask again just because you repeated
> that it is "named" and "error prone" and that it is so.


The "named" thing is about consistency. Named things ought to be
lvalues. That's the idea behind lvalues. Lvalues are things you have a
reusable handle on. Together with your desired rules being error
prone, it think it's a strong case for the current rules.

> There are features in C++ which are error prone - such as a
> reference to a temporary - and they do exist.


In my opinion, that's not a good enough reason to deliberately
introduce rules that make C++ more error prone.

> [...]
> But I am also one of those people for whom less typing [...]
> I am simply against tedious typing,
> and from this point of view this little extra safety is not
> worth it as much as for instance in the default memory ordering


That's your opinion. I don't agree with it. It's not only safety but
consistency w.r.t. named things being lvalues.

> (which _is_ a tradeoff: safety against performance).
>
> In most cases std::move is the only usage of the variable, at least
> in the standard library implementation. Funny enough, the first two
> cases I have found which do include multiple usage of the variable
> actually do use it after std::move, though with care. Here:
>
> * * * template<typename _Tp, typename _Del>
> * * * * explicit
> * * * * __shared_count(std::unique_ptr<_Tp, _Del>&& __r)
> * * * * : _M_pi(_S_create_from_up(std::move(__r)))
> * * * * { __r.release(); }


As long as it's clear what _S_create_from_up does (which is a private
static of the same class and thus part of the implementation which is
free to do whatever it likes as long as its behaviour matches the
specification) I'm fine with it.

You could even get rid of this std::move thing if you made
_S_create_from_up take an lvalue-ref-to-const. This function simply
observes __r and doesn't change it. I really don't know what the
author(s) thought while writing this. I probably would have made
_S_create_from_up to take a lref-to-const and ditched the std::move
call. This constructor taking an rref is perfectly fine. This is an
ownership transfer to __shared_count and the reference unique_ptr is
"released" of its duties.

> * * * template<typename _Tp1, typename _Del>
> * * * * __shared_ptr(std::unique_ptr<_Tp1, _Del>&& __r)
> * * * * : _M_ptr(__r.get()), _M_refcount()
> * * * * {
> * * * * * __glibcxx_function_requires(_ConvertibleConcept<_T p1*, _Tp*>)
> * * * * * _Tp1* __tmp = __r.get();
> * * * * * _M_refcount = __shared_count<_Lp>(std::move(__r));
> * * * * * __enable_shared_from_this_helper(_M_refcount, __tmp, __tmp);
> * * * * }
>
> Where is your safety?


Where's the read access of __r after its std::move call? And even it
there was one: As the implementer of unique_ptr and shared_ptr you can
of course rely on your own implementation's behaviour. This looks
perfectly fine to me. I don't know what you were getting at with this
example.

> Why would I have to type std::move in about 30
> other cases where std::move(var) is the only usage of var?


Because it's less error prone and more consistent with named things
being lvalues. Because it's a good idea to be explicit about when you
lose interest in the "value" of a named object. ;)

But I think it's a good idea to explore the possibility (and its
implications) of automatically treating named rvalue references and
names of automatic objects ON THEIR LAST USE as rvalues. The problem I
see here is that determining what use is actually the last one can by
very tricky. Consider loops, consider conditional branches, etc.

> Should
> I get compiler warnings for the above contructs? Could I not
> get those compiler warning if the standard implicitly did not
> make (some) rvalue-refs lvalues? The compiler does know how the value
> is bound so the answer to the last question is easy to find.


Sorry, I don't follow.

> > [...] you can only _initialize_ rvalue references to refer to
> > objects nobody cares about because either they don't have a handle on
> > them or they explicitly declared that they are not interested in them
> > (via std::move of std::forward).

>
> std::forward is only remotely related to std::move, their main similarity
> is syntactical - the only link is the case where a single rvalue reference
> can be both used and forwarded. Who's confused?


I'm not. std::forward is a kind of conditional move request that
depends (typically) on a template parameter. After forwarding an x
using std::forward<X>(x) to some other function or function object you
won't see many read accesses to x anymore in real code.

Cheers!
SG


All times are GMT. The time now is 02:20 AM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.