4

I'm confusing with rvalue reference,
is_rvalue_reference<decltype(a)>::value indicates that the a variable is rvalue reference,
but can't pass it to hello(int &&) function.

#include <iostream>
#include <string>
#include <array>
using std::cout;
using std::endl;
using std::boolalpha;
using std::string;
using std::is_rvalue_reference;
using std::move;


void hello(int && z) {};


int main(void) {
    int && a = 20;

    // a is_xvalue: true
    cout << "a is_xvalue: " << boolalpha << is_rvalue_reference<decltype(a)>::value << endl;

    // error C2664:  
    // 'void hello(int &&)': cannot convert argument 1 from 'int' to 'int &&'
    hello(a);       

    // compile ok.
    hello(move(a));

    return 0;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
火狐狸
  • 193
  • 1
  • 4
  • 2
    It may look strange but **all** named variables are lvalues. The moment you name `a` it has become an lvalue. See [this](https://stackoverflow.com/questions/32620750/why-are-rvalues-references-variables-not-rvalue) for more. – aafulei Dec 12 '19 at 01:52

3 Answers3

4

Types and value categories are two independent things.

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.

The type of a is int&&, i.e. rvalue-reference. But as a named variable, a is an lvalue and can't be bound to rvalue-reference. It seems confusing but try to consider them separately.

(emphasis mine)

The following expressions are lvalue expressions:

  • the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
4

While a may seem like a r-value it is not.

a is itself an l-value that contains an r-value reference.

You can take the address of a, but you can't take the address of move(a).

Please take a look at this article to really understand value categories specifically geared towards your question.

Moshe Rabaev
  • 1,892
  • 16
  • 31
3

a can be an identifier, but it can also be an expression. Identifiers and expressions are different things. Expressions have types and value categories, and identifiers only have types.

  1. In the line

    int&& a = 20;
    

    a is an identifier (id-expression), not an expression. It has type int&& and it has no value category. decltype(a) will return the type int&&:

    If the argument is an unparenthesized id-expression ..., then decltype yields the type of the entity named by this expression.

  2. In the line

    hello(a);
    

    a is an expression, not an identifier. Now it has a value category, which is lvalue, because a is a name of a variable (type doesn't matter):

    The following expressions are lvalue expressions:

    • the name of a variable, ... Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression; ...

    It also has a type, which is int, because the standard reads:

    If an expression initially has the type “reference to T”, the type is adjusted to T prior to any further analysis.

    To treat a as an expression in decltype, you parenthesize it: decltype((a)) will return int&, because for lvalue expressions of type T it returns T&:

    If the argument is any other expression of type T, and

    • if the value category of expression is xvalue, then decltype yields T&&;
    • if the value category of expression is lvalue, then decltype yields T&;

    int&& in hello(int&&) cannot bind to expressions with lvalue value category, compilation fails.

  3. In the line

    hello(std::move(a));
    

    std::move(a) is an expression. Its value category is xvalue:

    The following expressions are xvalue expressions:

    • a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x); ...

    Its type is also int. decltype(std::move(a)) will return int&&, because for xvalue expressions of type T it returns T&&. int&& in hello(int&&) can bind to xvalues of type int, compilation succeeds.

Evg
  • 25,259
  • 5
  • 41
  • 83
  • Each C++ expression (an operator with its operands, a literal, `a variable name`, etc.) is characterized by two independent properties: a type and a value category. The declaration `int a = 10` is not expression, but a variable name is expression, so `a` is expression and is lvalue, right? – 火狐狸 Dec 13 '19 at 04:43
  • @火狐狸, a variable name can be used in different contexts. In a declaration like `int a` it is not an expression, it is an identifier and it has no value category. When you use `a` to call a function, `a` becomes an lvalue expression. In an assignment like `b = a`, `a` is also an expression. – Evg Dec 13 '19 at 07:00
  • Upvote for explaining ```std::move``` in the answer! – User 10482 May 17 '20 at 08:58