Home > OS >  Why r-value becomes l-value?
Why r-value becomes l-value?

Time:08-03

Due to the fact that the general question "how std::forward works" is too complicated I decided to start with a question on a particular example:

#include <utility>
#include <iostream>

namespace
{
    void foo(const int&)
    {
        std::cout << "const l-value" << std::endl;
    }

    void foo(int&)
    {
        std::cout << "l-value" << std::endl;
    }

    void foo(int&&)
    {
        std::cout << "r-value" << std::endl;
    }

    template <typename T>
    void deduce(T&& x)
    {
        //foo(std::forward<T>(x));
        foo(x);
    }
}

int main()
{
    deduce(1);

    return 0;
}

The output:

l-value

Why?

We pass r-value and the argument of deduce is r-value, right? Why is foo(int&) called?

When I call directly

foo(1);

it prints r-value. When does it become l-value inside deduce?

EDIT1

If I add foo2 to the code above:

void foo2(int&& val)
{
    static_assert(std::is_same_v<decltype(val), int&&>);

    foo(val);
}

and then call it from main

foo2(1);

l-value is printed but not r-value as I probably expected.

So, as mentioned in comments and answers, val is l-value because it is a named variable and it does not matter if it is declared as int&& or int&. That is something that I did not know before.

CodePudding user response:

The thing that everyone seems to be confused about when being introduced to this is that value category is not a property of an object, variable or other entity. It is not part of a type or anything, which makes it confusing because we also talk about lvalue references and rvalue references which are distinct concepts from value categories. Value category is a property of individual expressions only.

An id-expression like x or val, naming a variable, is always a lvalue expression, no matter what type the named variable has or whether it is a reference or whether it is a local/global variable or a function parameter or a template parameter, etc. The expression naming a variable of rvalue reference type is therefore still a lvalue expression.

And the second kind of property of expressions, their types, have references stripped. So the value category of the expression x (or val) is lvalue and its type is int, although the type of the variable named x (or val) is int&& (and doesn't have any value category).

A function call expression like std::forward<T>(x) in which the return type of the selected function is a rvalue reference is a xvalue expression (a kind of rvalue expression). And again, the expression's type is always non-reference, here int.

For a full list of the value categories of every kind of expression, see the lists at https://en.cppreference.com/w/cpp/language/value_category.

CodePudding user response:

This

    template <typename T>
    void deduce(T&& x)

may casually look like a RValue reference, however it is not a RValue reference. (I misunderstood this for years until I attended a class on Universal References recently). When used in this way it is a Universal Reference.

It's subtly yet another one of those double-meanings of operators that you see elsewhere in C .... << >> | "inline" etc...

See here for more detail: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

This is an important snippet from there:

Given that rvalue references are declared using “&&”, it seems reasonable to assume that the presence of “&&” in a type declaration indicates an rvalue reference. That is not the case:

Widget&& var1 = someWidget;      // here, “&&” means rvalue reference

auto&& var2 = var1;              // here, “&&” does not mean rvalue reference

template<typename T>
void f(std::vector<T>&& param);  // here, “&&” means rvalue reference

template<typename T>
void f(T&& param);               // here, “&&”does not mean rvalue reference

To further trip you up, C 20 has a feature called "abbreviated function templates", keep an eye out for this; the above template parameter could be written as follows:

    void deduce(auto&& x)

The other part of your question is "how does std::forward work". Quintessentially, it works on the basis of "reference collapsing". See a couple of articles here:

CodePudding user response:

Consider the opposite: if it weren't 'change back'. In that case, if you were to use two foo(x) calls, the code would consider both times the T&& overload. Therefore, you couldn't specify where you can move from it.

Hence the standard is worded in a way that, when you write std::forward<T>(x), you'll be able to move from it; otherwise, it's a reference (lvalue). Technically, you're searching for the terms lvalue and xvalue in the standard.

  •  Tags:  
  • c
  • Related