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.
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") .
“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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With