This blog was last modified 421 days before.

Introduction to LValue and RValue

Recommend to read Meaning of Lvalues And Rvalues, which could help you to build a basic conception of these things in your mind.

  • LValue -> RValue Allowed
  • RValue -> LValue Disallowed

You can use const T& to reference a rvalue of type T:

const int& 10;

// which is equivalent to
int __internal_variable = 10;
const int& __internal_variable;

rValue reference in C++11

#include <iostream>
using namespace std;

int main()
{
    int &&intRef = 10;      // creating rvalue ref
    ++intRef;               // fine. rvalue ref could be changed
    cout << intRef << endl; // output: 11
    return 0;
}

Basically it allow you to refer to an rValue, and so that you can change and edit the rValue.

Named rValue Reference

As its name imply, a named rValue reference is a rValue ref that have a name lol. In the example above, the rValue ref intRef has a name, so it's a named rValue ref.

And here notice that, when you refer to a variable by name in C++, you always get a lValue, there is no exception.

See example below:

#include <iostream>
using namespace std;

void func(int &x)
{
    cout << "lValue ref version called" << endl;
    cout << x << endl;
}

void func(int &&x)
{
    cout << "rValue ref version called" << endl;
    cout << x << endl;
}

int main()
{
    int x = 1;
    int &&xRef = 2;

    func(x);
    func(xRef);
    func(3);
}

Output would be:

lValue ref version called
1
lValue ref version called
2
rValue ref version called
3

Notice that even if xRef is a rValue ref, the compiler still considered it as an lValue and call the lValue override version of func.'

Knowing this will help you understand how Prefect Forward in C++ works, which is the topic we will dive into later.

Optimize Program Using rValue Ref

In this part, we recommend this Move Sementics For Beginner, you can read this article first before continue reading next.

Before learning how, we should know what happened and why we need optimization. And since most of the optimization issue happened in C++ class, so we will focused on classes in the following part.

Copy Operation in C++

There are lots of scenarios we need to copy things.

  • Passing function parameter.
  • Return from a function.
  • Create new class instance from existing one. (Class ins2 = ins1; or Class ins2(ins1))
  • Copy assignment. (ins2 = ins1;)

Again, please read the recommend article above to know how rValue reference can help you solve this issue like a charm.

Perfect Forward

This is the real thing I want to discusss about in this post. Again, thoughts comes from this StackOverFlow Answer.

If we have a function func(param), or any other function, then how can we use a wrapper to wrap them and as if this wrapper not exist at the same time?

Solution 1: T&

template<typename T>
wrapper(T &x){
    func(x);
}

Is this prefect? No.

For example if func(int x), then func(1) is legal, but wrapper(1) doesn't work, since T &x don't allow an rvalue.

Solution 2: const T&

template<typename T>
wrapper(const T &x){
    func(x);
}

Is this prefect? No.

If func(int& x), then x in wrapper could not be passed to func(x), it won't work at very first. The compiler would complaint:

binding reference of type 'int&' to 'const int' discards qualifiers

Solution 3: T&&

template<typename T>
wrapper(T &&x){
    func(x);
}

Is this prefect? Really closed.

The deduce rule would be:

Original Type Deduced T Type of x (T&&) Example
T(lvalue) T& T& && -> T& int x = 1; wrapper(x); // wrapper<int&>(int &)
T&(lvalue ref) T& T& && -> T& int x = 1; int& y = x; wrapper(y); // wrapper<int&>(int &)
T(rvalue literal) T T&& wrapper(1); // wrapper<int>(int &&)
T&&(rvalue ref) T& T& && -> T& int&& x = 1; wrapper(x); // wrapper<int&>(int &)

Notice that:

  • lvalue variable will be deduced to T&.
  • rvalue ref will also be deduced to T&.

We can say that a named rvalue ref param is treated/converted into a lvalue inside this function!. Because the deduced result of a rvalue ref is the same with lvalue ref one.

But this solution has its problem, check example below:

#include <iostream>
using namespace std;

void func(int &a)
{
    a++;
    cout << a;
}

template <typename T>
void wrapper(T &&x)
{
    func(x);
}

int main()
{
    wrapper(1); // no bro this not ok, but compiler say NOTHING!
    return 0;
}

Why compile say ok to this code?

We did passed a rvalue literal, and T&& is successfully deduced to int && in this case. But notice here, we use T &&x to define variable x here. Do you remember something? We can say that a named rvalue ref param is treated/converted into a lvalue inside this function!! So now you know, even if the T&& is int &&x, the x passed to inner function func is still be considered as a lvalue ref.

Final Solution!

A subtle change upon the above solution is needed to get the final answer.

template<typename T>
wrapper(T &&x){
    func(static_cast<T&&>(x));
}

This time when we try to write something like wrapper(1), compiler will complaint:

cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

The thought of this change is simple. Compiler want to consider int&& x as a lvalue ref when passing to func(), and we don't allow this happen.

One thing we should know is that what will happen if we try to create reference to a type.

"[given] a type TR that is a reference to a type T:

  • Attempt to create the type lvalue reference to TR creates the type T&.
  • Attempt to create the type rvalue reference to TR creates the type TR.

In table form would be:

Original Ref Operation Result Type
TR & T&
TR && T&
T&&(T) & T&
T&&(T) && T&&

So if we using static_cast<T&&>(x), then if the x is a rvalue ref, its type could be correctly maintained when passing to inner func().

Finally to make this prettier, C++ provide us a synonym of static_cast<T&&>(x), that is std::forward<T>(x).

So the very final prefect forward solution would be:

template<typename T>
wrapper(T &&x){
    func(std::forward<T>(x));
}