Giving an rvalue ref to a function taking an rvalue ref

J

Juha Nieminen

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?
 
L

Luca Risolia

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.
 
S

SG

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
 
S

s0suk3

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.
 
S

SG

[...]
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
 
L

Luca Risolia

Dnia Mon, 20 Aug 2012 13:38:28 +0200, Luca Risolia napisal:


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.
 
G

Garrett Hartshaw

Dnia Wed, 22 Aug 2012 18:10:38 +0200, Luca Risolia napisal:


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.
 
T

Tobias Müller

Edek Pienkowski said:
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
 
S

SG

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
 
S

SG

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<_Tp1*, _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
 
H

Howard Hinnant

S

SG

Dnia Fri, 24 Aug 2012 02:20:16 -0700, SG napisal:
Edek said:
In most cases std::move is the only usage of the variable, [...]

My point is that it is up the author of any given code to know
when one would use a variable after std::move. Just like here:
knowing what can be assumed of taking an rvalue reference, futhermore
that usage is std::move and not std::forward-ing it later, one can know
it is safe. It is only partial safety.

I would go as far as saying that the code you showed for
__shared_count is non-idiomatic. The original idea was to overload
functions (or constructors) using lref-to-const and rref-to-non-const
parameters where the functions "logically" don't modify their
arguments. For example operator+ on strings is not "logically"
supposed to mutate its arguments. The rref overload is just an
optimization for the case that nobody would care about a mutation.
Here, we have just a single function that takes an rref for no good
reason. It could just as well be an lref or even an lref-to-const
since it doesn't even modify its argument.
Two points: often [std::move(named_rref)] is THE ONLY USE.
Probably.
[...]
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.

It depends on the author(s) of this code. Like in the above examples:
we know what that does, we are safe actually using it afterwards.

As I said, I consider this code not to be idiomatic. It's more like an
rref abuse.
I have little practice, but I think std::forward may not be a move
at all in the end.

I deliberately used the the term "move request" instead of "move".
Depending on T std::forward<T>(x) might have exactly the same effect
as std::move(x) and std::move is nothing but a "move request" or more
like a "I don't care about this variable's value anymore" declaration
-- at least that's how it should be used.

"Using it afterwards" may just be an assignment which should be
perfectly fine. Read accesses are more problematic and should not
happen in generic code at least.

Cheers!
SG
 
E

Edek Pienkowski

Dnia Sat, 25 Aug 2012 12:07:35 -0700, Howard Hinnant napisal:
Here is the first mention of it in the C++ committee technical papers:

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

The section "More on A&&" lays out the rationale for why variables
declared with rvalue reference type are treated as lvalues when used in
expressions.

Thanks, that was a good read. I was always curious how std::forward
without any special compiler treatment manages to forward lvalues _not_
to rvalue refs even is this choice is available. It is cool.

Also, I might have been less explicit in critisism - sorry for words.
Now that I know how arcane this feature is - and without any compiler
tricks - I will be much more happy while figuring out how to put
std::move under some keyboard shortcut in my favourite IDE. Oh,
when I will actually want to std::move something without being
accused of premature optimisation - needless to say rvalue-ref is most
people's favourite c++0x feature so that won't be a problem.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,563
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top