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();
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 but f7(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:
{
X __0(1);
__0.modify();
} // automatic cleanup of __0
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:
{
X __0(f5());
X __1(1);
__0.operator=(__1);
}
(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 :)
I wasn't entirely satisfied by the answers, so I took a look at:
"More Effective C++", Scott Meyers. Item 19: "Understand the origin of
temporary Objects"
. 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)
Meyer: "True temporary objects in C++ are invisible - they don't
appear in your source code. They arise whenever a non-heap object is
created but not named. Such unnamed objects usually arise in one of
two situations: when implicit type conversions are applied to make
function calls succeed and when functions return objects."
"Consider first the case in which temporary objects are created to
make function calls succeed. This happens when the type of object
passed to a function is not the same as the type of the parameter to
which it is being bound."
"These conversions occur only when passing objects by value or when
passing to a reference-to-const parameter. They do not occur when
passing an object to a reference-to-non-const parameter."
"The second set of circumstances under which temporary objects are
created is when a function returns an object."
"Anytime you see a reference-to-const parameter, the possibility
exists that a temporary will be created to bind to that parameter.
Anytime you see a function returning an object, a temporary will be
created (and later destroyed)."
The other part of the answer is found in: "Meyer: Effective C++", in
the "Introduction":
"a copy constructor is used to initialize an object with a different
object of the same type:"
String s1; // call default constructor
String s2(s1); // call copy constructor
String s3 = s2; // call copy constructor
"Probably the most important use of the copy constructor is to define
what it means to pass and return objects by value."
Regarding my questions:
f5() = X(1) //what is happening?
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:
void f7(X& x);
f7(f5);
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:
"C-style casts look like this: (T)expression //cast expression to be
of type T
Function-style casts use this syntax: T(expression) //cast expression
to be of type T"
So X(1)
is a function-style cast. 1
the expression is being cast to
type X
.
And Meyer says it again:
"About the only time I use an old-style cast is when I want to call an
explicit constructor to pass an object to a function. For example:
class Widget {
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); //create Widget from int
//with function-style cast
doSomeWork(static_cast<Widget>(15));
Somehow, deliberate object creation doesn't "feel" like a cast, so I'd
probably use the function-style cast instead of the static_cast in
this case."
Here is an example what actually happens when you execute your code. I've made some modifications to clarify the processes behind the scene:
#include <iostream>
struct Object
{
Object( int x = 0 ) {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
~Object() {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
Object( const Object& rhs ){std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;}
Object& operator=( const Object& rhs )
{
std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;
return *this;
}
static Object getObject()
{
return Object();
}
};
void TestTemporary()
{
// Output on my machine
//0x22fe0e: Object::Object(int) -> The Object from the right side of = is created Object();
//0x22fdbf: Object::Object(int) -> In getObject method the Temporary Unnamed object is created
//0x22fe0f: Object::Object(const Object&) rhs = 0x22fdbf -> Temporary is copy-constructed from the previous line object
//0x22fdbf: Object::~Object() -> Temporary Unnamed is no longer needed and it is destroyed
//0x22fe0f: Object& Object::operator=(const Object&) rhs = 0x22fe0e -> assignment operator of the returned object from getObject is called to assigne the right object
//0x22fe0f: Object::~Object() - The return object from getObject is destroyed
//0x22fe0e: Object::~Object() -> The Object from the right side of = is destroyed Object();
Object::getObject() = Object();
}
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:
-fno-elide-constructors