Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does the address of an lvalue reference to a prvalue represent?

When a function parameter is of type lvalue reference lref:

void PrintAddress(const std::string& lref) {
  std::cout << &lref << std::endl;
}

and lref is bound to a prvalue:

PrintAddress(lref.substr() /* temporary of type std::string */)

what does the address represent? What lives there?

A prvalue cannot have its address taken. But an lvalue reference to a prvalue can have its address taken, which is curious to me.

like image 633
mgiuffrida Avatar asked Mar 12 '17 20:03

mgiuffrida


People also ask

What is a Prvalue in C++?

C++ Value Categories prvalue A prvalue (pure-rvalue) expression is an expression which lacks identity, whose evaluation is typically used to initialize an object, and which can be implicitly moved from. These include, but are not limited to: Expressions that represent temporary objects, such as std::string("123") .

What is lvalue reference?

“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable. They are declared using the '&' before the name of the variable.

What do you mean by the term rvalue and lvalue?

An lvalue refers to an object that persists beyond a single expression. An rvalue is a temporary value that does not persist beyond the expression that uses it.


2 Answers

Inside the function lref is not a prvalue it is an lvalue and you can take the address of it.

There is a common misconception about rvalues vs. lvalues. A named parameter is always an lvalue. No matter whether it is a reference type that is bound to an rvalue. Through a const & reference type you can't even tell which kind of value category the object actually has at the point where the function is called. Rvalue references and non-const Lvalue references give you that information:

void foo(std::string& L, std::string&& R)
{
    // yeah i know L is already an lvalue at the point where foo is called
    // R on the other hand is an rvalue at the point where we get called
    // so we can 'safely' move from it or something...
}

The temporary string is a prvalue in the context of the caller (at the point PrintAddress is called). Within the context of the callee (in PrintAddress) lref is an lvalue reference because in this context it actually is an lvalue.

PrintAddress isn't aware of the limited lifetime of the passed argument and from PrintAddress' point of view the object is "always" there.

std::string q("abcd");
PrintAddress(q.substr(1)); // print address of temporary

is conceptually equivalent to:

std::string q("abcd");
{
    const std::string& lref = q.substr(1);
    std::cout << &lref << std::endl;
}

where the temporary experiences a prolongation of its lifetime to the end of the scope in which lref is defined (which is to the end of PrintAddress function scope in the present example).


what does the address represent? What lives there?

A std::string object containing the passed content.

And is it legal (in C++, and with respect to memory) to write to that address?

No, it would be legal if you'd use an rvalue reference:

void PrintAddressR(std::string&& rref) {
    rref += "Hello"; // writing possible
    std::cout << &rref << std::endl; // taking the address possible
}
// ...
PrintAddressR(q.substr(1)); // yep, can do that...

The same applies here: rref is an lvalue (it has a name) so you can take its address plus it is mutable.

like image 122
Pixelchemist Avatar answered Nov 12 '22 19:11

Pixelchemist


In short, because the prvalue's lifetime has been extended. By having its lifetime extended - by any reference -, it's an lvalue, and thus can have its address taken.


what does the address represent? What lives there?

The address represents an object, the object referenced by lref.

A prvalue is short lived, it doesn't live for long. In fact, it will be destroyed when the statement creating it ends.

But, when you create a reference to a prvalue (either an rvalue reference or a const lvalue reference), its lifetime is extended. Ref.::

An rvalue may be used to initialize a const lvalue [rvalue] reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.

Now it makes actually sense to take its address, as it is an lvalue for all intents and purposes. Now, that the prvalue has an indeterminate lifetime, it is an lvalue.

Taking the address of a prvalue doesn't make sense however, and that's probably why it is disallowed:

  • The value is destroyed after the next statements, so you can't do anything with the address, except maybe print it out.

  • If you take the address of something, the compiler is required to actually create the object. Sometimes, the compiler will optimize out variables that are trivial, but if you were to take the address of them, the compiler won't be allowed to optimize them out.

    Taking the address of a prvalue will thus result in the compiler being unable to elide the value completely, for no advantages whatsoever (see point 1).

like image 34
Rakete1111 Avatar answered Nov 12 '22 21:11

Rakete1111