Trying to understand whether using std::forward
with auto&&
variables is the right way to pass those variables to allow move.
Assume there is a function:
void moveWidget(Widget&& w);
And the caller - two variables to refer to rvalue and lvalue:
Widget w;
auto&& uniRefLV = w; // lvalue initialiser,
// uniRefLV's type is Widget&
auto&& uniRefRV = std::move(w); // rvalue initialiser,
// uniRefRV's type is Widget&&
We know that a variable of type auto&&
is a universal reference because there is a type deduction taking place. Which means both uniRefRV
and uniRefLV
are universal references.
In my example it is obvious that uniRefRV
is rvalue and uniRefLV
is lvalue but conceptually they are both universal references and if definition was different they could represent either rvalue or lvalue.
Now, I want to call moveWidget()
and perfect forward those universal references types. The guideline (by Scott Meyers) says:
Pass and return rvalue references via
std::move
, universal references viastd::forward
.
And unless I am completely misinterpreting the guideline it seems logical to use std::forward
. But let's consider all possible choices:
// (1) std::move:
moveWidget(std::move(uniRefLV)); // Compiles and looks fine
// but violates the guideline?
// (unconditionally casts lvalue to rvalue)
moveWidget(std::move(uniRefRV)); // Same as above - but not an issue here
// as we cast rvalue to rvalue
// (2) std::forward with Widget:
moveWidget(std::forward<Widget>(uniRefLV)); // Compiles, follows the guideline
// but doesn't look right - what if
// we didn't know Widget's type?
moveWidget(std::forward<Widget>(uniRefRV)); // Same as above
// (3) std::forward with decltype:
moveWidget(std::forward<decltype(uniRefLV)>(uniRefLV)); // Fails to compile! (VC10)
// follows the guideline
// has nice and short syntax :)
moveWidget(std::forward<decltype(uniRefRV)>(uniRefRV)); // Compiles fine
Do you think we should treat both references uniRefLV
and uniRefRV
equally and which of three options should we use for perfect forwarding?
You are misinterpreting the guideline. Or at least taking it too literally.
I think there are three important things to realise here.
First, all types are known here. The advice for universal references applies mostly for generic code with templates where you don't know at all if something is or takes an lvalue reference or an rvalue reference.
Second, the function takes an rvalue reference: you have to pass an rvalue. Period. There is no choice here.
And the logical conclusion is that you do not want to pass an universal reference: whatever you pass has to be an rvalue, it can never be an lvalue. Universal references can be lvalues (if they are instantiated as lvalue references). Passing an universal reference along means "I don't know what kind of reference this is, and I can pass it either as rvalue or lvalue, so I am passing it along exactly as I got it". The case in this question is more like "I know exactly what I must pass, so that's what I'll be passing".
Let's assume that the case isn't as simplistic as it seems. Instead of giveMeInt
we have something like this:
template <typename T>
typename complex_computation<T>::type giveMeSomething(T t);
And instead of moveMe
, you have something that actually takes a universal reference and therefore doesn't require an unconditional std::move
.
template <typename T>
void wantForwarded(T&&);
Now you actually need perfect forwarding.
auto&& uniRef = giveMeSomething(iDontKnowTheTypeOfThis);
wantForwarded(std::forward<decltype(uniRef)>(uniRef);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With