7

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; } 
1
  • 3
    It may look strange but all named variables are lvalues. The moment you name a it has become an lvalue. See this for more. Commented Dec 12, 2019 at 1:52

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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?
@火狐狸, 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.
Upvote for explaining std::move in the answer!
5

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;

Comments

5

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 (archived here) to really understand value categories specifically geared towards your question.

1 Comment

thank you very much, this article is what i want.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.