10

I have a code:

void f(int&& i) {
  auto lambda = [](int&& j) { (void)j; }
  lambda(i);
}

int main() {
  f(5);
}

Clang++ gives an error: no known conversion from 'int' to 'int &&' for 1st argument

Why the i changes its type to int when being passed to the lambda()?

abyss.7
  • 13,882
  • 11
  • 56
  • 100
  • 3
    It helps to distinguish between the variable named `i` which has type `int&&`, and the expression written `i` that is the result of evaluating that variable which has type `int` and value category `lvalue`. It's easy to conflate the two when we represent both with the same syntax. The same sort of thing happens with `int i` (variable of type `int`, lvalue expression of type `int`) and `int& i` (variable of type `int&`, lvalue expression of type `int`), but the difference between the type of the variable and the type of the expression that evaluates it becomes more important with rvalue refs. – Casey Feb 21 '14 at 17:51
  • @Casey: Yours is the best answer, in my opinion. – TonyK Feb 21 '14 at 17:59
  • @TonyK It doesn't stand on its own without the other answers (all three of which I upvoted), I just thought it would help to better understand them. Us experienced folk are used to it, but its very confusing to newer programmers that `i` means different things in different contexts. Rvalue references are *hard* to teach. – Casey Feb 21 '14 at 18:29

3 Answers3

15

i is of type int&&, that is, it's of type "rvalue reference to int." However, note that i itself is an lvalue (because it has a name). And as an lvalue, it cannot bind to a "reference to rvalue."

To bind it, you must turn it back to an rvalue, using std::move() or std::forward().

To expand a bit: the type of an expression and its value category are (largely) independent concepts. The type of i is int&&. The value category of i is lvalue.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • I totally miss the point - how the `lvalue` can have type `rvalue reference`, but be not able to bind to the same type `rvalue reference`? Are there any similar examples in C++ with other types? - Or should I just remember, that only rvalue references behave like that? – abyss.7 Feb 21 '14 at 17:42
  • @abyss.7: It having a "rvalue-reference" type doesn't make it an _rvalue_ in terms of value category. The name "rvalue-reference" was a bit of a silly choice, really. – Lightness Races in Orbit Feb 21 '14 at 17:45
  • 2
    RValueness isn't a characteristic of a variable, it's a characteristic of an expression. (well, except in some specific template-y cases). In the expression `lambda(i)`, both "lambda" and "i" are lvalues because they have names. In the expression from my answer, `lambda(std::move(i))` lambda is still an lvalue, i is still an lvalue, but std::move(i) is an rvalue. – Cogwheel Feb 21 '14 at 17:46
  • 3
    @Abyss Yes, there are. Look at `const char * p;` for example. `p` is a pointer to a constant character, but p itself is *not* constant. I find it helpful to mentally rewrite "rvalue reference" as "reference to rvalue". The expression that the reference was initialized with is an rvalue, not the reference itself. – fredoverflow Feb 21 '14 at 19:22
  • The type of *the expression* `i` is `int`. (Expressions don't have reference type). – M.M Mar 08 '18 at 19:53
7

There are two elements at work here:

  • Type: The parameter i has type int&&, or "rvalue reference to int", where "rvalue reference" is the name for the && feature, allowing binding rvalues to a reference;

  • Value category: This is the key. i has a name and so the expression naming it is an lvalue, regardless of its type or what the standard committee decided to call that type. :)

(Note that the expression that looks like i has type int, not int&&, because reasons. The rvalue ref is lost when you start to use the parameter, unless you use something like std::move to get it back.)

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • May I assume that the `i` actually gets type `int` (or `int&`?) after the function matching (with temporary rvalue object) is applied? Is it true in all situations where the `int&&` is used, that it's really just the `int` (or `int&`)? – abyss.7 Feb 21 '14 at 17:51
  • @M.M: As an expression, yes. I'll clarify. Ta – Lightness Races in Orbit Mar 08 '18 at 19:53
4

i is a name, and any object accessed by name is automatically an LValue, even though your parameter is marked as an rvalue reference. You can cast i back to an rvalue by using std::move or std::forward

void f(int&& i) {
  auto lambda = [](int&& j) { (void)j; };
  lambda(std::move(i));
}

int main() {
  f(5);
}
Cogwheel
  • 22,781
  • 4
  • 49
  • 67