Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is use of std::forward with auto&& the right thing to do

Tags:

c++

c++11

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 via std::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?

like image 293
Sereger Avatar asked Jul 02 '13 15:07

Sereger


2 Answers

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

like image 68
R. Martinho Fernandes Avatar answered Oct 30 '22 22:10

R. Martinho Fernandes


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);
like image 45
Sebastian Redl Avatar answered Oct 30 '22 21:10

Sebastian Redl