1
votes

Explain me, please, how it works? Why double && works for lvalue and rvalue? And why const double && don't work for lvalue?

template <typename U>
void function(U& var) {
    std::cout << var << std::endl;
}

int main()
{
    int var1 = 45;
    function(var1);
    function(45); //error
}

error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’ function(45); ////////////////////////////////////////////////

template <typename U>
void function(const U& var) {
    std::cout << var << std::endl;
}

int main()
{
    int var1 = 45;
    function(var1);
    function(45);
}

////////////////////////////////////////////////

template <typename U>
void function(U&& var) {
    std::cout << var << std::endl;
}

int main()
{
    int var1 = 45;
    function(var1);
    function(45);
}

////////////////////////////////////////////////

template <typename U>
void function(const U&& var) {
    std::cout << var << std::endl;
}

int main()
{
    int var1 = 45;
    function(var1);  // error
    function(45);
}

error: cannot bind ‘int’ lvalue to ‘const int&&’ function(var1);

3
Please include any error messages in your question. - Richard Critten
I believe all answers on the right side "Related" section can answer your question. - llllllllll

3 Answers

2
votes

double && does not work for lvalues. However, you don't have double && in your code, you have U && for a deduced U. That is special syntax for a so-called forwarding reference.

There's a special rule in C++ template deduction rules which says that when the parameter type is T&& where T is a deduced type, and the argument is an lvalue of type X, then X& is used instead of X for type deduction.

In your case, this means U is deduced to be int&, so the type of parameter var is collapsed from "int& &&" into int &.

You can learn more about this mechanism by searching for terms such as "forwarding reference" (historically called "universal reference") or "perfect forwarding."

2
votes

Let's summarize the rules at first.

  1. lvalues can be bound to lvalue-reference
  2. lvalues can't be bound to rvalue-reference
  3. rvalues can be bound to lvalue-reference to const
  4. rvalues can't be bound to lvalue-reference to non-const
  5. rvalues can be bound to rvalue-reference
  6. lvalues can be bound to forwarding-reference (and preserve its value category)
  7. rvalues can be bound to forwarding-reference (and preserve its value category)

The 1st case is lvalue-reference to non-const, then

function(var1); // rule#1 -> fine
function(45);   // rule#4 -> fail

the 2nd case is lvalue-reference to const, then

function(var1); // rule#1 -> fine
function(45);   // rule#3 -> fine

the 3rd case is forwarding-reference, then

function(var1); // rule#6 -> fine
function(45);   // rule#7 -> fine

the 4th case is rvalue-reference, then

function(var1); // rule#2 -> fail
function(45);   // rule#5 -> fine

It's worth noting the difference between rvalue-reference and forwarding-reference:

(emphasis mine)

function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template

0
votes

In your example, function(var1) is going to deduce var1 usage as lvalue. To make it compatible with rvalue-based template, you have to help your compiler a bit:

template <typename U>
void function(const U&& var) {
    std::cout << var << std::endl;
}

int main()
{
    int var1 = 45;
    function(std::move(var1));  // let convert lvalue to rvalue
    function(45);
}

P.S. And yes, you should always ask yourself "do I really need that rvalue reference, or I'm perfectly fine with that old good lvalue ref?"