9

I would like to implement a function that fills up a vector and then returns an rvalue reference. I tired something like:

std::vector<int> &&fill_list() {
  std::vector<int> res;
  ... do something to fill res ...
  return res;
}

int main(int argc, char **argv) {
  std::vector<int> myvec = fill_list();
  return 0;
}

but that doesn't work, I get the following error:

error: invalid initialization of reference of type 'std::vector<int>&&' from expression of type 'std::vector<int>'

So, all in all, how is the right way of doing it? I don't think I get rvalue references just yet.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Sambatyon
  • 3,316
  • 10
  • 48
  • 65
  • 6
    Even if that did compile, it would return a dangling reference. _Why_ do you want to return an rvalue reference? Why wouldn't you simply return by value? – ildjarn Mar 28 '12 at 19:48
  • As far as I know it wouldn't be a dangling reference, that is the whole point of the moving semantics in C++ 11, at least that is what I get from the documentation. – Sambatyon Mar 28 '12 at 19:52
  • 4
    I think you've got that point about rvalue references wrong: it is that a _value_ that would be destroyed anyway can be moved instead of needing to copy it. If you directly return an rvalue reference, there _is_ no value anymore because it has been destroyed _before_ you had the chance to move its contents. – leftaroundabout Mar 28 '12 at 19:53
  • 3
    @Sambatyon : If you returned by value, _then_ move semantics would be used (if NRVO didn't kick in). What you've shown is indeed a dangling reference, just the same as if your return type was `std::vector&`. – ildjarn Mar 28 '12 at 19:53
  • 4
    @Sambatyon: idljarn is correct, returning an *rvalue-reference* and a *lvalue-reference* makes no difference here the reference will point to an object that has just been destroyed. – David Rodríguez - dribeas Mar 28 '12 at 20:27
  • 1
    See also [Is returning by rvalue reference more efficient](http://stackoverflow.com/questions/1116641/is-returning-by-rvalue-reference-more-efficient) – Bo Persson Mar 28 '12 at 21:49

4 Answers4

30

You seem to be confused as to what an rvalue reference is and how it relates to move semantics.

First thing's first: && does not mean move. It is nothing more than a special reference type. It is still a reference. It is not a value; it is not a moved value; it is a reference to a value. Which means it has all of the limitations of a reference type. Notably, it must refer to a value that still exists. So returning a dangling r-value reference is no better than returning a dangling l-value reference.

"Moving" is the process of having one object claim ownership of the contents of another object. R-value references facilitate move semantics, but simply having a && does not mean anything has moved. Movement only happens when a move constructor (or move assignment operator) is called; unless one of those two things is called, no movement has occurred.

If you wish to move the contents of a std::vector out of your function to the user, you simply do this:

std::vector<int> fill_list() {
  std::vector<int> res;
  ... do something to fill res ...
  return res;
}

Given this usage of fill_list():

std::vector<int> myvec = fill_list();

One of two things will happen. Either the return will be elided, which means that no copying or moving happens. res is constructed directly into myvec. Or res will be moved into the return value, which will then perform move-initialization of myvec. So again, no copying.

If you had this:

std::vector<int> myvec;
myvec = fill_list();

Then again, it would be moved into. No copying.

C++11 knows when it's safe to implicitly move things. Returning a value by value rather than by reference or something is always a safe time to move. Therefore, it will move.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Sadly, C++11 is very strict about when it tries to actually move local variables. It only allows the compiler to do so if the conditions for copy elision apply, but copy elision can't be performed for whatever reason. This is overly strict, but can't be helped in this standard. – Xeo Mar 29 '12 at 12:02
1

The return statement is an error because you atempt to bind an rvalue reference (the return type) to an lvalue (the vector res). An rvalue reference can only be bound to an rvalue.

Also, as others already mentioned, returning a local variable when the return type is a reference type is dangerous, because the local object will be destroyed after the return statement, then you get a reference that refers to an invalid object.

If you want to avoid the copy construction during the return statement, just using a non-reference type might already works due to a feature called copy elision. the vector res in your fill_list function may be directly constructed into the vector myvec in your main function, so no copy or move construction is invoked at all. But this feature is allowed by Standard not required, some copy construction is not omitted in some compiler.

Cosyn
  • 4,404
  • 1
  • 33
  • 26
0

For a discussion of rvalue references you can read what Bjarne Stroustrup, the author of C++, has to say about them here:

http://www2.research.att.com/~bs/C++0xFAQ.html#rval

Addressing your specific example, the short answer is that due to the Named Return Value Optimization - which is a de facto standard C++ compiler feature even pre-C++11 - if you simply return-by-value the compiler will tie up res and myvec efficiently like you want:

std::vector<int> fill_list() {
    std::vector<int> res;
    ... do something to fill res ...
    cout << &res << endl; // PRINT POINTER
    return res;
}

int main(int argc, char **argv) {
     std::vector<int> myvec = fill_list();
     cout << &myvec << endl; // PRINT POINTER
     return 0;
}

The two "PRINT POINTER" lines will print the same pointer in both cases.

The vector myvec and the vector res in the above will be the same vector with the same storage. No copy constructor or move constructor will be called. In a sense res and myvec will be two aliases of the same object.

This is even better than using a move constructor. myvec is constructed and "filled up" in-place.

The compiler achieves this by compiling the function in an "inplace" mode overlaying an immediate stack position in the callers stack frame with the callees local result variable, and simply leaving it there after the callee returns.

In this circumstance we say that the constructor has been elided. For more information see here:

http://en.wikipedia.org/wiki/Return_value_optimization

In the event that you were assigning the result of fill_list in a non-constructor context, than as a return-by-value results in an xvalue (short for "expiring" value, a type of rvalue), the move assignment operator of the target variable would be given preference if it is available.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • "In this circumstance we say that the copy construction has been elided." Note that this also applies to move construction. – Nicol Bolas Mar 28 '12 at 20:32
  • Yes, both. What's the collective term for both "copy construction" and "move construction".. hehe move/copy construction I guess – Andrew Tomazos Mar 28 '12 at 20:34
  • "*`T&&` is only needed for **overload resolution**.*" This is a pretty huge oversimplification. – ildjarn Mar 28 '12 at 20:58
  • @ildjarn: In what other context is it needed? – Andrew Tomazos Mar 28 '12 at 20:58
  • Perfect forwarding, which also involves reference collapsing (which is only possible with `&&`). – ildjarn Mar 28 '12 at 21:26
  • 2
    RVO is a red herring in this instance. It *is* good advice to write code that can benefit from it, but that doesn't help understand the issue at hand. – Luc Danton Mar 28 '12 at 21:31
  • @ildjarn: std::move and std::forward are only relevant if there is an underlying overload resolution between an rvalue and lvalue parameter in the first place - but ok I've replaced that discussion with a link to Stroustrup. – Andrew Tomazos Mar 28 '12 at 22:38
  • 1
    "*std::move and std::forward are only relevant if there is an underlying overload resolution between an rvalue and lvalue parameter in the first place*" Huh? Where did you get this idea? It makes no sense at all. In any case, I didn't downvote, so no need to appease me. ;-] (Also, the OP is confused to begin with, hence asking the question in the first place; that he marked your answer with the green tick doesn't make it more relevant or correct.) – ildjarn Mar 28 '12 at 22:44
  • @ildjarn: If it doesn't make sense to you than you don't understand rvalue references. std::move and std::forward have no use except to modify which overload is picked. Look at the source code of their implementation and it will become clear. – Andrew Tomazos Mar 28 '12 at 23:00
  • 1
    "*std::move and std::forward have no use except to modify which overload is picked.*" Patently wrong -- I would assert that _you_ don't understand rvalue references. `std::move` turns an lvalue into an rvalue, and `std::forward` prevents an rvalue from incorrectly decaying into an lvalue during perfect forwarding. Your obstinance is indeed tempting me to downvote after all... Do yourself a favor and go read [this answer](http://stackoverflow.com/a/5481588/636019). – ildjarn Mar 28 '12 at 23:09
  • @ildjarn: Sigh. Yes, correct, and what good is controlling whether something is an rvalue or an lvalue except to control which overload is picked when there are different versions? If there is only a `void f(const X&)` available (and no `void f(X&& x)` then there is no point in calling `f(move(x))` or `f(forward<...>(x))` is there? – Andrew Tomazos Mar 28 '12 at 23:24
  • Because rvalues and lvalues can have different _semantics_, e.g. in the behavior of extending the lifetime of temporaries. You're being extremely narrow-minded. – ildjarn Mar 28 '12 at 23:27
  • @ildjarn: What is an example of where you would use std::move or std::forward to alter the lifetime of temporaries? I'm open minded, you may be wrong, very wrong or utterly wrong - open to all three possibilities. – Andrew Tomazos Mar 28 '12 at 23:30
  • 1
    I wouldn't use `std::move` or `std::forward` to alter the lifetime of temporaries, nor did I say I would. Let's make this kindergarten simple for you: 1) `std::move` and `std::forward` affect lvalue-ness or rvalue-ness. 2) Lvalue-ness or rvalue-ness affect semantics beyond what overloads are selected. 3) "*std::move and std::forward have no use except to modify which overload is picked.*" is thus an oversimplification at best, but really just wrong. Enjoy your "green tick", ignorance doesn't win out very often on SO. – ildjarn Mar 28 '12 at 23:33
  • @ildjarn: You are right, it is simple: The logical negation of my statement that you have challenged is: _std::move and std::forward have a use except to modify which overload is picked_. If this statement is true, as you claim it is, than provide an example of a use of move and forward that has a purpose other than modifying an overload resolution. – Andrew Tomazos Mar 28 '12 at 23:46
  • @user1131467: "If this statement is true, as you claim it is, than provide an example of a use of move and forward that has a purpose other than modifying an overload resolution." Very well. I have a function `foo(Type &)`, and I want to forward to it. So my forwarding function is `template fwd(T &&t) {foo(std::forward(t));}` What this does is ensure that if I pass `fwd` a temporary, it will fail *exactly* as it would if I'd called `foo` directly (though with a different error). This has nothing to do with overload resolution; there's exactly and only one overload. – Nicol Bolas Mar 29 '12 at 04:27
  • @user1131467: Even better, let's say that we had `foo(Type t)` instead of taking a `Type&`. Still just one function. If you pass an lvalue, you get a copy. If you pass an rvalue, you get a move. You only get one move or copy. – Nicol Bolas Mar 29 '12 at 06:08
  • @NicolBolas: In your first example, the nonexistance of the overload foo(Type&&) causes the error. In your second example the overload resolution is on the constructor of Type (which is just a function with two or more overloads: one overload is a move constructor the other is a copy constructor). – Andrew Tomazos Mar 29 '12 at 16:30
-1

If you just remove the && from your function it should work, but it will not be a reference. fill_list() will create a vector and return it. During the return a new vector will be created. The first vector that was created inside fill_list() will be copied to the new vector and then will be destroyed. This is the copy constructor's work.

Israel Unterman
  • 13,158
  • 4
  • 28
  • 35