Think about it this way, you used std::forward<T>
in func
, so likewise, to make sure the parameter is forwarded as an Rvalue reference, you have to do the same in the recursive function:
void testRef(int && param)
{
std::cout << "Rvalue reference" << std::endl;
// Here's the thing I can't get. Why param is lvalue reference here??
testRef( param );
testRef(std::forward<int &&>(param)); // now it will stay an Rvalue reference
testRef(std::move(param)); // make it an Rvalue reference
}
The reason we need std::forward
or std::move
is because param
is of type int&&
which is an lvalue (i.e. an rvalue reference parameter is an lvalue expression when you use it).
Behind the scenes these templates will eventually perform a static_cast<int &&>
which yields an xvalue expression (which is also classified as an rvalue expression.) An xvalue expression binds to rvalue reference parameters.
This can be seen by looking at Clang's syntax tree for the following function:
rvalue reference parameter (which binds to rvalue expressions)
vvvvvvvvvvv
void testRef(int&& param)
{
//std::move(param);
lvalue expression of type int&&
vvvvv
static_cast<int &&>(param);
^^^^^^^^^^^^^^^^^^^^^^^^^^
xvalue expression
(considered an rvalue expression which binds to rvalue reference parameters)
}
Abstract syntax tree for the function above:
TranslationUnitDecl
`-FunctionDecl <line:3:1, line:7:1> line:3:6 testRef 'void (int &&)'
|-ParmVarDecl <col:14, col:21> col:21 used param 'int &&'
`-CompoundStmt <line:4:1, line:7:1>
`-CXXStaticCastExpr <line:6:5, col:30> 'int' xvalue static_cast<int &&> <NoOp>
`-DeclRefExpr <col:25> 'int' lvalue ParmVar 0x55a692bb0a90 'param' 'int &&'
A shorthand way of explaining that a reference parameter becomes an lvalue would be to say that when it has a name (id-expression) it is an lvalue.