21

What is exactly happening here? Why is this an error?

void f(int &&);

int && i = 5;

f(i);

Isn't it a bit counterintuitive?

I would expect i to be a rvalue reference, and so be able to pass it to f(). But I get an error;

no known conversion from int to int &&

So I guess i is not an rvalue reference after declaration?

Niall
  • 30,036
  • 10
  • 99
  • 142
Jorge González Lorenzo
  • 1,722
  • 1
  • 19
  • 28

3 Answers3

29

I see why you are confused. The thing to remember is that whenever you have a variable name, you have an l-value.

So when you say:

int i = 0; // lvalue (has a name i)

And also

int&& i = 0; // lvalue (has a name i)

So what is the difference?

The int&& can only bind to an r-value so:

int n = 0;
int i = n; // legal

BUT

int n = 0;
int&& i = n; // BAD!! n is not an r-value

However

int&& i = 5; // GOOD!! 5 is an r-value

So when passing i to f() in this example you are passing an l-value, not an r-value:

void f(int &&);

int&& i = 5; // i is an l-value

f(i); // won't accept l-value

The situation is actually a little more complicated than I have presented here. If you are interested in a fuller explanation then this reference is quite thorough: http://en.cppreference.com/w/cpp/language/value_category

Galik
  • 47,303
  • 4
  • 80
  • 117
  • I think it's a great answer. The only slight thing I'd suggest is, perhaps make it a bit more clear in that first sentence, because often `this` can be thought of as a variable (though it isn't really), but then `this` is a prvalue. Can you think of any good way to address this point, or if it's really relevant after all? – Yam Marcovic Feb 10 '16 at 12:43
  • 1
    @YamMarcovic I know the situation can be a little more complicated but I think for learning purposes it is better to present what is simple and relevant to the example the OP is trying to understand. What I will do is add a link for further information. – Galik Feb 10 '16 at 13:08
  • @Galik Sounds reasonable. Thanks. – Yam Marcovic Feb 10 '16 at 13:11
20

There's a basic distinction here between is a and binds a. For example:

void f(int &&);

declares a function accepting a parameter that can only be initialized with an rvalue reference to a (type convertible to) int.

int && i = 5;

declares an lvalue that can only be initialized with an rvalue reference to a (type convertible to) int. Thus, in simple terms,

f(i);

tries to pass an lvalue reference to an int to a function accepting only rvalue references to an int. So it doesn't compile.

To tell the compiler to cast an lvalue to an rvalue, thereby utilizing move constructors where applicable (though not in the case of an int), you can use std::move().

f(std::move(i));
Yam Marcovic
  • 7,953
  • 1
  • 28
  • 38
  • I would add information about `std::move` in this case. – Zereges Feb 10 '16 at 11:44
  • @Zereges Good idea. Added. – Yam Marcovic Feb 10 '16 at 11:46
  • 4
    This is **almost** an excellent answer, but slightly inaccurate. _"declares an lvalue that can only be assigned an rvalue reference to an int."_ No, it cannot be "assigned" anything, certainly not an rvalue reference (because assigning to a reference actually assigns to the thing it is bound to, but this is initialization not assignment). It declares an lvalue, `i`, of rvalue-reference type, that can only be **initialized** with an rvalue (not an rvalue-reference, an rvalue). – Jonathan Wakely Feb 10 '16 at 12:28
  • _"To force the compiler to treat lvalue references as rvalue references"_ should be "To force the compiler to treat lvalues as rvalues", or even better, "To tell the compiler to cast an lvalue to an rvalue". – Jonathan Wakely Feb 10 '16 at 12:29
  • @Yam `std::forward` would do, too, wouldn't it? – LogicStuff Feb 10 '16 at 12:30
  • 2
    @LogicStuff, if you forward as an rvalue, yes, but that's pointless here. If you know you want an rvalue, use `std::move`. If you want to forward something as either lvalue or rvalue, depending on its original value category, use `std::forward`. It's completely pointless to write `std::forward(i)` instead of `std::move(i)`. – Jonathan Wakely Feb 10 '16 at 12:32
  • @JonathanWakely Re assignment point, doh! Thanks, will edit. Re rest of stuff: ditto. – Yam Marcovic Feb 10 '16 at 12:40
  • In the declaration `int && i = 5;`, `i` is an rvalue reference, **not** an lvalue reference, and `5` is an _rvalue_ of type `int`, **not** an rvalue reference. – cpplearner Feb 11 '16 at 11:29
  • I think you confused l/rvalue references with l/rvalues. They are distinct concepts. – cpplearner Feb 11 '16 at 11:36
  • @cpplearner It's a bit confusing, but `i` itself is actually an lvalue. For instance, you can take its address. This was the main confusion I was trying to sort out. – Yam Marcovic Feb 11 '16 at 11:48
  • 1
    @YamMarcovic Yes, `i` is an lvalue **and an rvalue reference**. – cpplearner Feb 11 '16 at 11:54
  • @cpplearner See [Value Categories](http://en.cppreference.com/w/cpp/language/value_category) `Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression` – Yam Marcovic Feb 11 '16 at 13:44
18

Does it have a name?
Is it addressable?

If the answer to both is "yes", it's a L-Value.

In this snippet: i has a name, i has an address (you can write &i), so it's a l-value.

f(&&) gets r-value-reference as a parameter, so you need to turn l-value to r-value reference, which can be done with std::move.

f(std::move(i));
David Haim
  • 25,446
  • 3
  • 44
  • 78
  • Note: there are also some lvalues which don't have names. Also, "Is it addressable" is a circular definition. By "addressable", I assume you mean, "can unary `&` be applied to it?"; however the definition of unary `&` includes that it can be applied to lvalues only! – M.M Feb 10 '16 at 13:16