Why can't I pass an rvalue-reference as it is to another function in C++11?
Asked Answered
T

3

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()?

Torritorricelli answered 21/2, 2014 at 17:34 Comment(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.Coaler
@Casey: Yours is the best answer, in my opinion.Ellinger
@Ellinger 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.Coaler
P
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.)

Perversion answered 21/2, 2014 at 17:47 Comment(2)
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&)?Torritorricelli
@M.M: As an expression, yes. I'll clarify. TaPerversion
D
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.

Dymoke answered 21/2, 2014 at 17:36 Comment(5)
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?Torritorricelli
@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.Perversion
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.Endocrinology
@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.Karli
The type of the expression i is int. (Expressions don't have reference type).Mokas
P
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.)

Perversion answered 21/2, 2014 at 17:47 Comment(2)
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&)?Torritorricelli
@M.M: As an expression, yes. I'll clarify. TaPerversion
E
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);
}
Endocrinology answered 21/2, 2014 at 17:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.