![]() |
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? |
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. |
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 |
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. |
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 |
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. |
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. |
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 |
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 |
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 08:44 PM. |
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.