9

I have used std::move and std::forward in C++. My question is: how are these functions actually implemented by the standard library?

If an lvalue is something you can get the address of, and an rvalue is exclusively not an lvalue, how can you actually implement these references?

Do these new facilities allow for something like:

auto x = &(3); 

or something like that? Can you get a reference to an rvalue that isn't just a std::move/forward returned lvalue?

Hopefully these questions make sense. I couldn't find good information on Google, just tutorials on perfect forwarding, etc.

Barry
  • 286,269
  • 29
  • 621
  • 977
user3816764
  • 489
  • 5
  • 22

3 Answers3

11

How is it possible to get a reference to an rvalue?

Conceptually, an rvalue expression creates a temporary object, or sometimes denotes an existing object. That can be bound to a reference like any other object; but, to avoid confusion, the language only allows that for rvalue and const lvalue references.

I have used std::move and std::forward in C++. My issue is how this is actually implemented by the compiler?

move simply returns an rvalue reference to its argument, equivalent to

static_cast<typename remove_reference<T>::type&&>(t)

The result of the function call is an rvalue (specifically, an xvalue), so it can be bound to an rvalue reference where the function argument couldn't. This allows you to explicitly move from an lvalue, using move to convert it to an rvalue, while not allowing you to accidentally move from it.

forward is similar, but overloaded to return an rvalue reference to an rvalue or rvalue reference, and an lvalue reference to anything else.

If an l-value is something you can get the address of

That's more or less correct. The official definition is that the expression "designates a function or an object", and those are things that have addresses.

and an r-value is exclusively not an l-value

Not really. Simplifying slightly, an expression is either a lvalue or an rvalue, but can be converted from one to the other. An lvalue can be implicitly converted to an rvalue; converting the other way can be done with a cast, as move does.

how can you actually implement these references?

Just like any other reference - as an alias for, or a pointer to, the object it's bound to. The only difference is which kinds of expression can be used to denote (and possibly create) the object that's bound to the reference.

Do these new facilities allow for something like auto x = &(3);

That attempts to take the address of an rvalue directly, which isn't allowed. Since the question is about references, not pointers, the following are allowed, binding a reference to a temporary object (whose lifetime is extended to match the reference):

auto && rvalue = 3;
auto const & const_lvalue = 3;

while it's not allowed to bind it to a non-const lvalue reference

auto & lvalue = 3;  // ERROR
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
6

I cannot call a function: void foo(string* bar) like this: foo(&string("Hello World!")) or I get an error:

error: taking address of temporary

I also cannot call a function: void foo(string& bar) like this: foo(string("Hello World!")) or I get an error:

error: invalid initialization of non-const reference of type 'std::string& {aka std::basic_string&}' from an rvalue of type 'std::string {aka std::basic_string}'

What C++11 has provided me the ability to do is to make an rvalue reference, so I can call a function: void foo(string&& bar) like this: foo(string("Hello World!"));

Furthermore, internally to foo I can get the address of the object passed in by an rvalue reference:

void foo(string&& bar){
    string* temp = &bar;

    cout << *temp << " @:" << temp << endl;
}

It seems like the OP has a really good grip on rvalues. But this explanation of them was helpful to me, and may be to others. It goes into a bit of detail about why C++03 allowed constant references to rvalues, versus C++11's rvalue references.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • This definitely answers all of my questions. Thank you. edit: And thanks for the link, I'll have a look. – user3816764 Feb 11 '15 at 16:23
  • *"Furthermore, internally to `foo` I can get the address of the r-value:"* I disagree: Rvalues, or expressions in general, don't have addresses - objects have addresses. Hence, `&bar` does not give you the address of an rvalue. You'll get the address of an object that is referred to by the expression `bar` (a subexpression of `&bar`), and this expression `bar` is an lvalue. – dyp Feb 11 '15 at 18:34
  • @dyp OK, I agree with what you are saying... Would you feel comfortable with this edit: "Furthermore, internally to `foo` I can get the address of the object passed in by an r-value reference." – Jonathan Mee Feb 11 '15 at 18:44
  • Well, I don't think that's incorrect or imprecise. But it may not cover the essence of how you achieve it to get the address of the object: You'll need an lvalue referring to the object to get its address, hence you *change the value category of the expression*, or rather *create a new lvalue expression referring to the same object*. For example, the anti-move `template T& as_lvalue(T&& t) { return t; }` – dyp Feb 11 '15 at 18:47
  • @dyp Your "anti-move" is very interesting. I've never thought about doing that. I've made a first pass edit... but help me out here, when you say: "You'll need an lvalue referring to the object to get its address." Are you saying that `bar` is in fact an l-value internally to `foo`? – Jonathan Mee Feb 11 '15 at 19:28
  • Yes, the *id-expression* `bar` (i.e., the *name* `bar`) is an lvalue, even though it refers to a variable that has type *rvalue reference to string*. I currently have no simple rule to classify lvalues. The best I have is "all names but enumerators are lvalue-expressions", followed by "since the rvalue reference allows you to access the bound object multiple times, using the name of the rvalue reference must be an lvalue-expression to not trigger multiple moves". – dyp Feb 11 '15 at 19:54
  • @dyp I appreciate you taking the time to explain all this to me, but since we've totally left the scope of the original question, I decided to start a new question here: http://stackoverflow.com/q/28483250/2642059 If you have time to bestow some of your understanding upon me there I'd be grateful. – Jonathan Mee Feb 12 '15 at 16:56
1

Basically, compiler magic. The Standard describes the rules, the compiler maker just has to figure out how to implement the rules.

In practice, references are either optimized out or implemented as pointer on CPU level.

std::move isn't really special in that sense. It has a lvalue reference as input, and an rvalue reference as output. The compiler just has to apply the rvalue reference rules to the input.

Similarly, the goal of std::forward<T> is just to tell the compiler to apply a different set of rules to the argument, rules which happen to be defined so that perfect forwarding works. The function itself does nothing.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Right, I know that std::move and std::forward are basically like "Take an L value, *treat it* as an r-value". But is it possible to actually get the address of a 'pure' r-value? Like auto x = &(i + 3); I guess. Is *that* possible? I'd still think not, right? edit: And you've answered my original question. I should put in the main question that I also am curious about getting an r-value reference without std::move/ std::forward – user3816764 Feb 11 '15 at 16:12
  • "is it possible to actually get the address of a 'pure' r-value?" Yes, by creating a temporary object to hold the value. That object has an address. The language doesn't allow you to directly take the address with `&`, but you can bind it to various types of reference. To get an *rvalue* reference to an *lvalue* without `move`, you need a cast (which is all that `move` does). – Mike Seymour Feb 11 '15 at 16:23