Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it illegal to take the address of an rvalue temporary?

Tags:

c++

According to " How to get around the warning "rvalue used as lvalue"? ", Visual Studio will merely warn on code such as this:

int bar() {    return 3; }  void foo(int* ptr) {  }  int main() {    foo(&bar()); } 

In C++ it is not allowed to take the address of a temporary (or, at least, of an object referred to by an rvalue expression?), and I thought that this was because temporaries are not guaranteed to even have storage.

But then, although diagnostics may be presented in any form the compiler chooses, I'd still have expected MSVS to error rather than warn in such a case.

So, are temporaries guaranteed to have storage? And if so, why is the above code disallowed in the first place?

like image 668
Lightness Races in Orbit Avatar asked Jan 06 '12 19:01

Lightness Races in Orbit


People also ask

Can you take the address of an rvalue?

2) non-modifiable lvalues, which are const. rvalue — The expression that refers to a disposable temporary object so they can't be manipulated at the place they are created and are soon to be destroyed. An address can not be taken of rvalues. An rvalue has no name as its a temporary value.

Where is rvalue stored?

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

What are Rvalues?

rvalues are defined by exclusion. Every expression is either an lvalue or an rvalue, so, an rvalue is an expression that does not represent an object occupying some identifiable location in memory. For example, An assignment expects an lvalue as its left operand, so the following is valid −

What is rvalue C++?

An rvalue is an expression that is not an lvalue. Examples of rvalues include literals, the results of most operators, and function calls that return nonreferences. An rvalue does not necessarily have any storage associated with it.


2 Answers

Actually, in the original language design it was allowed to take the address of a temporary. As you have noticed correctly, there is no technical reason for not allowing this, and MSVC still allows it today through a non-standard language extension.

The reason why C++ made it illegal is that binding references to temporaries clashes with another C++ language feature that was inherited from C: Implicit type conversion. Consider:

void CalculateStuff(long& out_param) {     long result;     // [...] complicated calculations     out_param = result; }  int stuff; CalculateStuff(stuff);  //< this won't compile in ISO C++ 

CalculateStuff() is supposed to return its result via the output parameter. But what really happens is this: The function accepts a long& but is given an argument of type int. Through C's implicit type conversion, that int is now implicitly converted to a variable of type long, creating an unnamed temporary in the process. So instead of the variable stuff, the function really operates on an unnamed temporary, and all side-effects applied by that function will be lost once that temporary is destroyed. The value of the variable stuff never changes.

References were introduced to C++ to allow operator overloading, because from the caller's point of view, they are syntactically identical to by-value calls (as opposed to pointer calls, which require an explicit & on the caller's side). Unfortunately it is exactly that syntactical equivalence that leads to troubles when combined with C's implicit type conversion.

Since Stroustrup wanted to keep both features (references and C-compatibility), he introduced the rule we all know today: Unnamed temporaries only bind to const references. With that additional rule, the above sample no longer compiles. Since the problem only occurs when the function applies side-effects to a reference parameter, it is still safe to bind unnamed temporaries to const references, which is therefore still allowed.

This whole story is also described in Chapter 3.7 of Design and Evolution of C++:

The reason to allow references to be initialized by non-lvalues was to allow the distinction between call-by-value and call-by-reference to be a detail specified by the called function and of no interest to the caller. For const references, this is possible; for non-const references it is not. For Release 2.0 the definition of C++ was changed to reflect this.

I also vaguely remember reading in a paper who first discovered this behavior, but I can't remember right now. Maybe someone can help me out?

like image 163
ComicSansMS Avatar answered Oct 11 '22 14:10

ComicSansMS


Certainly temporaries have storage. You could do something like this:

template<typename T> const T *get_temporary_address(const T &x) {     return &x; }  int bar() { return 42; }  int main() {     std::cout << (const void *)get_temporary_address(bar()) << std::endl; } 

In C++11, you can do this with non-const rvalue references too:

template<typename T> T *get_temporary_address(T &&x) {     return &x; }  int bar() { return 42; }  int main() {     std::cout << (const void *)get_temporary_address(bar()) << std::endl; } 

Note, of course, that dereferencing the pointer in question (outside of get_temporary_address itself) is a very bad idea; the temporary only lives to the end of the full expression, and so having a pointer to it escape the expression is almost always a recipe for disaster.

Further, note that no compiler is ever required to reject an invalid program. The C and C++ standards merely call for diagnostics (ie, an error or warning), upon which the compiler may reject the program, or it may compile a program, with undefined behavior at runtime. If you would like your compiler to strictly reject programs which produce diagnostics, configure it to convert warnings to errors.

like image 32
bdonlan Avatar answered Oct 11 '22 15:10

bdonlan