2

Here's the code that troubles me

‍‍‍‍‍#include <iostream>

#include "DataItem.h"


void testRef( const int & param )
{
    std::cout << "Lvalue reference" << std::endl;
}

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 );
}


template<class T>
void func( T && param )
{
    testRef( std::forward<T>( param ) );
}


int main2() 
{
    int a = 12;
    func( a );

    std::cout << "=================" << std::endl;

    func( 14 );

    std::cout << "=================" << std::endl;

    return 0;
}

When I call testRef() in testRef( int && param ) I presume that as far as param is rvalue reference than ravalue function will called (and yes, eternal recursion will happen). But lvalue function is called. Why?

Yura
  • 969
  • 14
  • 33

1 Answers1

3

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.

wally
  • 10,717
  • 5
  • 39
  • 72
  • I presume that `std::forward` is necessary when function parameter is universal reference. Am I right that `std::move` is more appropriate for this case? – Yura Mar 17 '19 at 18:18
  • 1
    `std::move` clearly expresses the intent in this case. You want the parameter to be an Rvalue reference. `std::forward` expresses the intent that you might not know what it is but you want it to stay the same and match the relevant function. Better explanation [here](https://stackoverflow.com/a/48999182/1460794). – wally Mar 17 '19 at 18:20
  • As @Sombrero Chicken said if I have reference name it's lvalue. This means that we know for sure that `param` in `void testRef(int && param)` if lvalue. This intents me to use `std::move`. Now everything is clear, great thanks – Yura Mar 17 '19 at 18:22
  • Just to make it clear: This is *not* how `std::forward` should be used. When you're in a non-generic context and you want to move, please just use `std::move`. – ComicSansMS Mar 17 '19 at 20:07