In Eckel, Vol 1, pg:367
//: C08:ConstReturnValues.cpp
// Constant return by value
// Result cannot be used as an lvalue
class X {
int i;
public:
X(int ii = 0);
void modify();
};
X::X(int ii) { i = ii; }
void X::modify() { i++; }
X f5() {
return X();
}
const X f6() {
return X();
}
void f7(X& x) { // Pass by non-const reference
x.modify();
}
int main() {
f5() = X(1); // OK -- non-const return value
f5().modify(); // OK
// Causes compile-time errors:
//! f7(f5());
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
} ///:~
Why does f5() = X(1)
succed? What is going on here???
Q1. When he does X(1)
- what is going on here? Is this a constructor call -
shouldn't this then read X::X(1);
Is it class instantiation - isn't class
instantiation something like: X a(1);
How does the compiler determine what
X(1)
is?? I mean.. name decoration takes place so.. X(1)
the constructor
call would translate to something like: globalScope_X_int
as the function
name.. ???
Q2. Surely a temporary object is used to store the resulting object that X(1)
creates and then would't that be then assigned to the object f5()
returns
(which would also be a temporary object)? Given that f5()
returns a temporary
object that will be soon be discarded, how can he assign one constant temporary
to another constant temporary??? Could someone explain clearly why:
f7(f5());
should reult in a constant temporary and not plain old f5();
This is indeed a constructor call, an expression evaluating to a temporary object of type
X
. Expressions of the formX([...])
withX
being the name of a type are constructor calls that create temporary objects of typeX
(though I don't know how to explain that in proper standardese, and there are special cases where the parser can behave differently). This is the same construct you use in yourf5
andf6
functions, just omitting the optionalii
argument.The temporary created by
X(1)
lives (doesn't get destructed/invalid) until the end of the full expression containing it, which usually means (like in this case with the assignment expression) until the semicolon. Likewise doesf5
create a temporaryX
and return it to the call site (insidemain
), thus copying it. So in main thef5
call also returns a temporaryX
. This temporaryX
is then assigned the temporaryX
created byX(1)
. After that is done (and the semicolon reached, if you want), both temporaries get destroyed. This assignment works because those functions return ordinary non-constant objects, no matter if they are just temprorary and destroyed after the expression is fully evaluated (thus making the assignment more or less senseless, even though perfectly valid).It doesn't work with
f6
since that returns aconst X
onto which you cannot assign. Likewise doesf7(f5())
not work, sincef5
creates a temporary and temporary objects don't bind to non-const lvalue referencesX&
(C++11 introduced rvalue referencesX&&
for this purpose, but that's a different story). It would work iff7
took a const referenceconst X&
, as constant lvalue references bind to temporaries (but thenf7
itself wouldn't work anymore, of course).All your questions boil down to a rule in C++ which says that a temporary object (one that has no name) cannot be bound to a non-const reference. (Because Stroustrup felt it could provoke logical errors...)
The one catch, is that you can invoke a method on a temporary: so
X(1).modify()
is fine butf7(X(1))
is not.As for where the temporary is created, this is the compiler job. The rules of the language precise that the temporary should only survive until the end of the current full-expression (and no longer) which is important for temporary instances of classes whose destructor has a side-effect.
Therefore, the following statement
X(1).modify();
can be fully translated to:With that in mind, we can attack
f5() = X(1);
. We have two temporaries here, and an assignment. Both arguments of the assignment must be fully evaluated before the assignment is called, but the order is not precise. One possible translation is:(the other translation is swapping the order in which
__0
and__1
are initialized)And the key to it working is that
__0.operator=(__1)
is a method invocation, and methods can be invoked on temporaries :)Here is an example what actually happens when you execute your code. I've made some modifications to clarify the processes behind the scene:
You have to know that on most modern compilers the copy construction will be avoided. This is because the optimization which is made (Return Value Optimization) by the compiler. In my output I have removed explicitly the optimization to show what actually happens according to the standard. If you want to remove this optimization too use the following option:
I wasn't entirely satisfied by the answers, so I took a look at:
. Regarding Bruce Eckel's coverage of "Temporaries", well, as I suspect and as Christian Rau directly points out, it's plain wrong! Grrr! He's (Eckel's) using us as guinea pigs!! (it would be a good book for newbies like me once he corrects all his mistakes)
The other part of the answer is found in: "Meyer: Effective C++", in the "Introduction":
Regarding my questions:
Here a new object isn't being initialized, ergo this is not initialization(copy constructor): it's an assignment (as Matthieu M pointed out).
The temporaries are created because as per Meyer (top paragraphs), both functions return values, so temporary objects are being created. As Matthieu pointed out using pseudo-code, it becomes:
__0.operator=(__1)
and a bitwise copy takes place(done by the compiler).Regarding:
ergo, a temporary cannot be created (Meyer: top paragraphs). If it had been declared:
void f7(const X& x);
then a temporary would have been created.Regarding a temporary object being a constant:
Meyer says it (and Matthieu): "a temporary will be created to bind to that parameter."
So a temporary is only bound to a constant reference and is itself not a "const" object.
Regarding: what is
X(1)
?Meyer, Item27, Effective C++ - 3e, he says:
So
X(1)
is a function-style cast.1
the expression is being cast to typeX
.And Meyer says it again: