简体   繁体   中英

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.

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.

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).

In simple English:

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

Any object that has a name is an lvalue , hence any use of lref within the scope of the funtion above is an lvalue use.

When you called the function with:

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

Of cause, lref.substr() produces a temporary which is an rvalue , but rvalues can bind to (have its lifetime extended by) const lvalue references or rvalue references.


Even if you provided an rvalue overload, for the fact it has a name , its an "lvalue of something" within its scope, example:

#include <string>
#include <iostream>

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

void PrintAddress(std::string&& `rref`) {
  std::cout << "RValue: " << &rref << std::endl;  //You can take address of `rref`
}

int main(){
    std::string str = "Hahaha";
    PrintAddress(str);
    PrintAddress(str.substr(2));
}

Just remember:

In C++, any object(whether value type , reference type or pointer type ) that has a name is an lvalue

Also know that some expressions produce lvalues too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM