10

From what I've read and seen you cannot bind an expression that is an rvalue to an lvalue reference. What I have seen however is that you can bind an rvalue to an rvalue reference and since a named rvalue reference is inherently an lvalue, you can bind it to an lvalue reference. What is the reason behind disallowing binding an rvalue to an lvalue reference. Is it for optimization purposes?

Take this example:

#include <iostream>

using std::cout;

void bar ( int& b ) {

    cout << "bar " << b << "\n";
    b = 3;
}

void foo ( int&& a ) {

    cout << a << "\n";
    bar(a);
    cout << a << "\n";
}

int main ( int argc, char ** argv ) {

    foo(1);
}
edaniels
  • 708
  • 5
  • 23
  • 5
    Just because the language allows you to contrive disaster doesn't mean there's a problem with the language. On the other hand, allowing lvalue references to bind to rvalues makes is essentially impossible to write functions that can be reasoned about sanely. – Kerrek SB Oct 14 '14 at 20:20
  • related http://stackoverflow.com/questions/2577623/some-clarification-on-rvalue-references – Piotr Skotnicki Oct 14 '14 at 20:36
  • This has been asked before (more than one time). I'm sure you can find duplicates if you search thoroughly enough. Stroustrup says (either in D&E or in TC++PL, can't remember) that it prevents a mistake: `void twice(int& x) { x*=2; } double x = 1.0; twice(x); // doesn't modify x` – dyp Oct 14 '14 at 20:38
  • 2
    Related: [How come a non-const reference cannot bind to a temporary object?](http://stackoverflow.com/q/1565600) – dyp Oct 14 '14 at 20:43
  • @MattMcNabb I'm not objecting to anything. Simply asking about the semantics – edaniels Oct 14 '14 at 22:20
  • @edaniels ok, in that case I'm not sure what your posted code has to do with the question :) (see the q linked by dyp) – M.M Oct 14 '14 at 22:21
  • In the posted code we bind an rvalue reference to an rvalue (a <- 1). Then we bind that rvalue reference (really an lvalue) to an lvalue reference (b <- a (<- 1) ). This is valid and works. What you cannot do is skip the middle step and just do b <- 1 because of the rules of the language. The question asks why is the latter method not allowed if we are still allowed to do the former method. It seems like the answer is that C++ will try to avoid letting us shoot ourselves in the foot unless we really want to. – edaniels Oct 14 '14 at 22:25
  • You **can** directly bind an lvalue reference to an rvalue. `const T& lvalueref = T();` – Ben Voigt Oct 14 '14 at 22:31
  • Sorry I meant a lvalue reference to a non const – edaniels Oct 14 '14 at 22:32
  • You cannot make the name of an lvalue disappear (without name hiding and without removing the object it refers to). The only place you can, namely return statements etc, already treat lvalues as rvalues and therefore allow binding an lvalue to an rvalue reference. On the other hand, you can easily give an rvalue a name and refer to it multiple times afterwards. That's why an rvalue reference is an lvalue. – dyp Oct 15 '14 at 00:08

3 Answers3

10

It's a fundamental rule of C++ and it prevents bugs:

int foo();

int& x = 3;      // whoops
int& y = foo();  // whoops (sometimes)

"Rvalue references" (a set of types; not to be confused with actual rvalues) were created at least in part so that you can still do this if you really want to:

int&& x = 3;     // oh, go on then *sigh*
int&& y = foo(); // you'd better be sure!

In the previous examples, I bind (or attempt to bind) objects "referenced" by an rvalue expression to a reference.

Now, I shall bind the object named by an lvalue expression to a reference:

int i = foo();

int& x = i;      // no problem michael

And to make sure that you really meant to obtain an rvalue reference from an lvalue expression, introducing the incredibly poorly-named std::move:

int&& x = std::move(i);  // doesn't move anything

These later rules came much, much later than the original, fundamental rule that has doubtless prevented many bugs over the past twenty years.

Note that Visual Studio has historically accepted T& x = bar() where T is a user-defined type; go figure.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • can you explain the `(sometimes)` ? – M.M Oct 14 '14 at 22:20
  • @Matt: `int& foo() { static int xyzzy = 42; return xyzzy; }`. Predates my addition of `int foo();`. Perhaps I should remove it. – Lightness Races in Orbit Oct 14 '14 at 22:21
  • So when you say rvalue references are a category of type then I would assume lvalue references are a category of type. So does it mean that when I call bar in my example I am converting an rvalue reference to an lvalue reference since they are types? Or is the underlying object that is being referred to just being referred to become referred to as an lvalue reference? – edaniels Oct 14 '14 at 22:31
  • @edaniels: Almost! Except that, inside `foo`, when you name `a`, you are creating an _lvalue `int`_ expression. It's not `int&&` any more. :) – Lightness Races in Orbit Oct 14 '14 at 22:39
  • adding on that, if the parameter a was int& a, when you name a would it then be an lvalue int expression not an int&? Could you elaborate on what you mean creating an lvalue int expression? Do you mean that anytime within the body of a that I refer to a, I am actually creating an int expression? Doesn't that kind of ignore the underlying fact that what is being stored is temporary? – edaniels Oct 14 '14 at 22:44
  • Also just so I have my semantics down, when I say for instance binding an lvalue reference to rvalue reference I mean: rvalue& = lvalue&;. Should I be thinking of it as lvalue& = rvalue&; instead or does the direction of binding not matter? – edaniels Oct 14 '14 at 22:46
  • @edaniels: [Correct](http://pastebin.com/5vNsJdtW) (and yes it absolutely ignores the fact that the original object is a temporary and it doesn't really matter unless you manage to create dangling references: that becomes the programmer's problem, though), and I didn't understand your last question. – Lightness Races in Orbit Oct 14 '14 at 22:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/63067/discussion-between-edaniels-and-lightness-races-in-orbit). – edaniels Oct 14 '14 at 22:53
  • @LightnessRacesinOrbit I asked a couple of more questions in the discussion posted so that we don't bloat this :) – edaniels Oct 14 '14 at 23:06
  • @LightnessRacesinOrbit Well I think `int& x = 42;` is *not* supported in MSVC, they only allow this if there is already an object (i.e. only for class types). – dyp Oct 14 '14 at 23:58
  • @dyp: You're right; [it's only for user-defined types](http://stackoverflow.com/a/16495384/560648) (I don't use VS). – Lightness Races in Orbit Oct 15 '14 at 09:01
  • @edaniels: Answered there. – Lightness Races in Orbit Oct 15 '14 at 14:09
6

What is the reason behind disallowing binding an rvalue to an lvalue reference?

No answer to this question can be complete without a reference to the invaluable and distinguished source, The Design and Evolution of C++ by Bjarne Stroustrup.

In section 3.7 Bjarne writes:

I made one serious mistake, though, by allowing a non-const reference to be initialized by a non-lvalue. For example:

void incr(int& rr) { rr++; }

void g()
{
    double ss = 1;
    incr(ss);    // note: double passed, int expected
                 // (fixed: error in Release 2.0)
}

Because of the difference in type the int& cannot refer to the double passed so a temporary was generated to hold an int initialized by ss's value. Thus incr() modified the temporary, and the result wasn't reflected back to the calling function.

I highly recommend The Design and Evolution of C++ for understanding many of the "why questions" one might have, especially regarding the rules that were laid down prior to the C++98 standard. It is an informative and fascinating history of the language.

Niall
  • 30,036
  • 10
  • 99
  • 142
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

When you call foo(1), a temporary int equal to 1 is created, and int&& a is bound to it. You can freely change it. The same way you can write int&& a = 1;. a itself is an lvalue, so you can bind lvalue reference to it.

You may read a great article Universal References by Scott Meyers which explains rvalue and lvalue references in detail.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90