9

I'm curious about why I can't compile the following code.

It's nonsense code (I know), but I originally faced the problem in some other code using templates with perfect forwarding and such.

I managed to narrow the problem down to std::move / std::forward / std::remove_reference, and I'm curious why it needs a temporary in the first place...

#include <utility>
#include <stdio.h>

struct Foo {
    Foo(Foo&& other) {
        printf("Foo::(Foo&&) %p\n", this);
    }
    Foo() {
        printf("Foo::() %p\n", this);
    }
    ~ Foo() {
        printf("Foo::~Foo() %p\n", this);
    }
};

void test(Foo&& t)
{
    // OK: Works fine and "f1" is is constructed with Foo::Foo(Foo&& other)
    // Foo f1 = std::move(t);

    // ERROR: Here it is trying to  bind a temporary Foo to a non-const lvalue
    // I can't figure out why it needs to create a temporary.
    Foo& f2 = std::move(t);
}

int main()
{
    Foo foo;
    test(std::move(foo));
}

Compiling with Clang (3.7), it gives me the following error:

23 : error: non-const lvalue reference to type 'Foo' cannot bind to a temporary of type 'typename std::remove_reference<Foo &>::type' (aka 'Foo')
Foo& f2 = std::move(t);
^ ~~~~~~~~~~~~
1 error generated.
Compilation failed

I understand I can't bind a temporary to a non-const reference, and there are plenty of questions answering why that is not allowed.

I would expect the code to just carry a reference to foo in main up to Foo& f2, thus not needing a temporary.

Visual Studio accepts the code, but GCC and Clang fail to compile it; although Visual Studio is not as strict of course.

Niall
  • 30,036
  • 10
  • 99
  • 142
RuiFig
  • 483
  • 3
  • 13
  • 1
    Try to think about the difference between `int& x = 12;` and what you are trying to do (`Foo& f2 = std::move(t);`). What error is the compiler giving you for each of these statements? – giant_teapot Apr 08 '16 at 10:46

5 Answers5

9

Well:

Foo& f2 = std::move(t);

f2 is a reference, so where are you moveing to? You're not actually moving at all.

std::move returns an rvalue reference; you cannot assign this to an lvalue reference variable (consider that an rvalue reference can be a reference to a temporary). So, the compiler complains that you are assigning a reference to a temporary (because std::move creates what the compiler considers to be a reference to a temporary, that is, an rvalue reference).

There's no actual creation of a temporary; it's just that std::move returns an rvalue reference, and you are not allowed to assign such to an lvalue reference. (The only possible "temporary" is the one referred to by the t parameter, which is declared as an rvalue reference; it so happens that your example passes in something that is not a temporary, via move, but it could just as easily have passed a reference to an actual temporary).

So in short, the problem is not that it needs a temporary, but that you are assigning an rvalue reference (which potentially refers to a temporary) to an lvalue-reference variable. The Clang error message is a little misleading, because it implies the existence of a temporary, whereas an rvalue reference might not actually refer to a temporary. GCC produces this instead:

test2.cc: In function 'void test(Foo&&)': test2.cc:23:24: error:
invalid initialization of non-const reference of type 'Foo&' from an
rvalue of type 'std::remove_reference<Foo&>::type {aka Foo}'
     Foo& f2 = std::move(t);
davmac
  • 20,150
  • 1
  • 40
  • 68
  • I know. That's why I said this code is nonsense. This was part of a bigger code base. I simplified to showcase the problem with minimal code. I'm trying to understand why it need the temporary in the first place. – RuiFig Apr 08 '16 at 10:46
  • @RuiFig it doesn't need the temporary - but `move` returns an rvalue reference, which is a "reference to a temporary". `t` is the temporary. – davmac Apr 08 '16 at 10:52
  • (well, `t` is a reference to the temporary - the "temporary" which was passed in). – davmac Apr 08 '16 at 10:58
  • @davmac `t` is a reference to `foo`, and `foo` is not a temporary – M.M Apr 08 '16 at 11:07
  • @M.M please read my answer, and recognize that I used quotes around the word "temporary" in my comment above. I am perfectly aware that `foo` is not really a temporary. – davmac Apr 08 '16 at 11:08
  • 1
    @Niall 's answer was the first to point me in the right direction, but yours ended up elaborating on a few things that improved further my mental model of the problem. Thank you. Most notably, I was missing the point that my code does not need a temporary and thus I expected it to work (since it's just passing along a reference), but `std::move(t)` might or might not (from the compiler POV) refer to a temporary, and thus can't be assigned to `Foo& f2`. – RuiFig Apr 08 '16 at 12:31
9

I would expect the code to just carry a reference to foo in main up to Foo& f2, thus not needing a temporary.

It is not as simple as carrying forward the reference. The std::move here is a nice way of saying "cast this lvalue such that I can use it as an rvalue". This is in essence why it is not working.

From the cppreference;

In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.

Foo& f2 requires an lvalue to bind the reference, you are providing an rvalue reference - hence the error.

There are no temporaries being created, in this regard the error message is misleading. You are casting references around to allow for value category conversions. Once these conversions are done, the move can be executed, via the appropriate move constructor or assignment operator.


As a side note: VS probably allows this because it has a non-standard extension that allows rvalues to bind to non-const lvalue references (but it will warn you of this with a higher warning level /W4).

Niall
  • 30,036
  • 10
  • 99
  • 142
  • 1
    See this [Q&A](http://stackoverflow.com/q/3601602/3747990) for more info on the value categories. – Niall Apr 08 '16 at 11:25
  • You got me on the right path to improve my mental model of value categories. Thank you. – RuiFig Apr 08 '16 at 12:29
3

There are no temporaries in your code.

Foo& f2 = std::move(t); fails because an lvalue reference cannot bind to an xvalue.

To fix this you could write Foo& f2 = t;.

The clang error message is bogus, perhaps they use the same error message for all attempts to bind non-const lvalue reference to rvalue (an xvalue is an rvalue) and didn't bother to make a different one for this case because it is relatively rare.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • RuiFig is talking about temporaries because of the diagnostic message from clang. `error: non-const lvalue reference to type 'Foo' cannot bind to a temporary of type 'typename std::remove_reference::type' (aka 'Foo')` – Mankarse Apr 08 '16 at 10:59
  • Indeed. I have no temporaries that I can see. But the error clearly mentions a temporary. The answer given by Niall seems the best so far. I need to brainstorm a bit and get my head around it. – RuiFig Apr 08 '16 at 10:59
-1

When you move an object you need to move it to a real location. In the case of f1 you have created an instance of the class foo and are moving t to there. In the case of f2 you have not specified a location to move t into, so implicitly you are telling the compiler to move t to some temporary location and then return a reference to that location. This is not allowed by the compiler.

Niall
  • 30,036
  • 10
  • 99
  • 142
Conor
  • 1,028
  • 1
  • 8
  • 15
-1

rvalue is basically a temporary object returned by some expression. It is destroyed right after being used. Taking reference/pointer to deleted object makes no sense, so it is prohibited in c++

Andrei R.
  • 2,374
  • 1
  • 13
  • 27