Is this legal?

M

Michael Sparks

In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

struct inside_t
{
};

struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside) : _inside(inside)
{
}
};

int main()
{
const outside_t& outside=outside_t(inside_t());
// now, outside._inside is referencing to nowhere
return 0;
}
 
V

Victor Bazarov

Michael Sparks said:
In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

struct inside_t
{
};

struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside) : _inside(inside)
{
}
};

int main()
{
const outside_t& outside=outside_t(inside_t());
// now, outside._inside is referencing to nowhere
return 0;
}

Is this an interview or a homework question? Read about the lifetime of
a temporary if there is a constant reference bound to it.

Victor
 
C

Claudio Jolowicz

In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

struct inside_t
{
};

struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside) : _inside(inside)
{
}
};

int main()
{
const outside_t& outside=outside_t(inside_t());
// now, outside._inside is referencing to nowhere
return 0;
}

A temporary object is not destroyed if it is used to initialize a
reference. Example:

int main()
{
const int& r = int();
std::cout << r << std::endl; //r still references the temporary object
return 0;
}
 
M

Michael Sparks

Claudio Jolowicz said:
A temporary object is not destroyed if it is used to initialize a
reference. Example:

Yes, I understand that. The question is about a more specific issue.

In main(), the temporary is not used to initialize a reference. It is
simply passed as an argument to a constructor.
It is in the constructor where it is bound to the const reference, and at
that point, it isn't known to be a temporary - it is just a parameter.

Suppose I had broken the code into two compilation units:

--------------------- first compilation unit ---------------------

struct inside_t
{
};

struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside);
};

outside_t::eek:utside_t(const inside_t& inside) : _inside(inside)
{
}

--------------------- end first compilation unit ---------------------

--------------------- second compilation unit ---------------------

struct inside_t
{
};

struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside);
};

int main()
{
const outside_t& outside=outside_t(inside_t());
// now, outside._inside is referencing to nowhere
return 0;
}

--------------------- end second compilation unit ---------------------

Now, it is clear to see that when compiling main(), the compiler could not
have any clue that the temporary inside_t is used to initialize a const
reference. Therefore, it will destroy it after that line.

Also, I have verified this behavior experimentally with MSVC. I more or
less assumed that the behavior was correct, and that MSVC compiled it
correctly - the "is this legal" question was more rhetorical than anything.

What I'm really interested in is *why* this is legal.

Mike
 
M

Michael Sparks

Victor Bazarov said:
Is this an interview or a homework question? Read about the lifetime of
a temporary if there is a constant reference bound to it.

No, this is not a homework or interview question.
Please see my reply to Claudio Jolowicz.

Also, unless I'm mistaken it has little to do with the lifetime of a
temporary if there is a const reference bound to it.
If that is incorrect, please enlighten me.

Mike
 
S

SaltPeter

Michael Sparks said:
In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

Its your responsability to guarentee an object's scope and lifetime, not the
compiler's. In this case, the outside_t structure invokes a temporary
inside_t object by *default* since outside_t's constructor *requires* a
reference to some inside_t object.

The compiler needs not satisfy the existance of the inside_t object after
the outside_t's cstor was invoked.

<snip>
____________________
Lets define an inside_t object, pass it by reference in order to construct
your outside_t object (which then initializes the inside_ member with a
reference that actually refers to something real). Note that the inside_t
object is never "part-of" the outside_t object, yet its now destroyed last.

#include <iostream>

struct inside_t
{
inside_t() { std::cout << "inside_t cstor\n"; }
~inside_t() { std::cout << "inside_t d~stor\n"; }
};

struct outside_t
{
outside_t(const inside_t& r_inside) : inside_(r_inside)
{
std::cout << "outside_t cstor\n";
}
~outside_t() { std::cout << "outside_t d~stor\n"; }

const inside_t& inside_;
};

int main()
{
inside_t in; // cstor invoked
outside_t out(in); // cstor invoked, in passed by reference
// display member inside_
std::cout << "&out.inside_ = " << &out.inside_ << std::endl;

const outside_t& ref_out = out;
std::cout << "reference ref_out = " << &ref_out << std::endl;
std::cout << "in's address is " << &in << std::endl;

return 0;
}
_________
Think of it this way: write a letter, put it in an envelope with some
address specified and stick an appropriate postage stamp on it. Mail it.
Nobody is going to build a home/apartment at destination in order to
guarentee the delivery of that one piece of mail. A reference works exactly
in the same way.
 
V

Victor Bazarov

Michael Sparks said:
No, this is not a homework or interview question.
Please see my reply to Claudio Jolowicz.

Also, unless I'm mistaken it has little to do with the lifetime of a
temporary if there is a const reference bound to it.
If that is incorrect, please enlighten me.

If there are two const references in the program, and both are bound
to the two temporaries in the program, then the lifetime of those two
temporaries has _everything_ to do with it. I don't know how to
enlighten you. Just take a look at the code.

Victor
 
J

Jorge Rivera

int main()
{
const int& r = int();
std::cout << r << std::endl; //r still references the temporary object
return 0;
}

Try that code with you own class, and see if what you're saying holds
true... (No destructor called)

JLR
 
R

Rolf Magnus

Michael said:
Yes, I understand that. The question is about a more specific issue.

In main(), the temporary is not used to initialize a reference. It is
simply passed as an argument to a constructor.

Which gets a reference as parameter, which is initialized with the
temporary.
 
J

Jakob Bieling

In main(), the temporary is not used to initialize a reference.

It is!
It is
simply passed as an argument to a constructor.

And that argument is of type 'inside_t const&,' meaning you bind the
temporary to a constant reference.
It is in the constructor where it is bound to the const reference, and at
that point, it isn't known to be a temporary - it is just a parameter.

It is known as a reference to a constant 'inside_t.' Meaning, you are
using a 'inside_t const&' to initialize another 'inside_t const&'.

hth
 
J

Jakob Bieling

Also, unless I'm mistaken it has little to do with the lifetime of a
temporary if there is a const reference bound to it.
If that is incorrect, please enlighten me.

Binding a temporary to a constant reference changes the lifetime of that
temporary.

hth
 
B

Benoit Mathieu

Michael said:
In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

[code removed

A simple test shows that this code does not work.
See :
- outside object is destroyed only at return 0, because it
has been assigned to a reference. It is destroyed at the
same time as the reference. This is ok.
- inside object is destroyed before the outside objet that
refers to it, this is a bug.

// Test.cpp :

#include <iostream>
struct inside_t
{
inside_t() { std::cout << this << " inside_t constructed"
<< std::endl; }
~inside_t() { std::cout << this << " inside_t destroyed"
<< std::endl; }
};
struct outside_t
{
const inside_t& _inside;
outside_t(const inside_t& inside) : _inside(inside)
{
std::cout << this << " outside_t constructed (refers to "
<< &_inside << ")" << std::endl;
}
~outside_t() { std::cout << this << " ouside_t destroyed"
<< std::endl; }
void print_me() const
{
std::cout << this << " outside still alive (refers to "
<< &_inside << ")" << std::endl;
}
};

int main()
{
const outside_t& outside=outside_t(inside_t());
std::cout << "before end" << std::endl;
outside.print_me();
return 0;
}


// Output:
0xbffff940 inside_t constructed
0xbffff950 outside_t constructed (refers to 0xbffff940)
0xbffff940 inside_t destroyed
before end
0xbffff950 outside still alive (refers to 0xbffff940)
0xbffff950 ouside_t destroyed
 
V

Victor Bazarov

Benoit Mathieu said:
Michael said:
In this code, the temporary inside_t instantiated in main() goes out of
scope by the time we hit the next line.
MSVC 2003 doesn't complain about that even at the highest warning level. Is
it legal? If so, why?

[code removed

A simple test shows that this code does not work.
See :
- outside object is destroyed only at return 0, because it
has been assigned to a reference. It is destroyed at the
same time as the reference. This is ok.
- inside object is destroyed before the outside objet that
refers to it, this is a bug.

So, binding a temporary _directly_ to a const reference is what causes
the temporary to live as long as the reference, however, preserving
that const reference by initialising a different const reference with
it is NOT OK, since the lifetime of the first reference could be shorter
than that of the different const reference. Did I understand the gist
of your example correctly?

Here is a different illustration of the same problem:

#include <iostream>
using namespace std;

struct A
{
char n;
A(char n) : n(n) { cout << "A(" << n << ")\n"; }
~A() { cout << "~A(" << n << ")\n"; }
};

void foo(const A& ra)
{
static const A& a = ra; // preserve the reference
static const A& aa = A('2'); // initialise another reference
}

int main()
{
foo(A('1'));
cout << "before end\n";
return 0;
}

I guess my reply to the OP was in error.

Victor
 
C

Claudio Jolowicz

Yes, I understand that. The question is about a more specific issue.

In main(), the temporary is not used to initialize a reference. It is
simply passed as an argument to a constructor.
It is in the constructor where it is bound to the const reference, and at
that point, it isn't known to be a temporary - it is just a parameter.

In fact, the temporary is used to initialize a constant reference - the
constructor's parameter. The lifetime of the temporary ends when the
reference parameter goes out of scope, i.e. at the end of the
constructor! This is most probably not what was intended, since the
destructor will be called while the member reference is still bound to
the temporary. Thanks for pointing this out.

Here's yet another example to illustrate the problem (see also Victor's
last post 2004-04-18 16:45:41 GMT):


#include <iostream>
using namespace std;

struct inside_t
{
inside_t() { cout << "inside_t::inside_t()" << endl; }
~inside_t() { cout << "inside_t::~inside_t()" << endl; }
};

struct outside_t
{
outside_t(const inside_t& ri) : _ri(ri) {}
private:
const inside_t& _ri;
};

int main()
{
outside_t foo(inside_t());
cout << "Shouldn't have destroyed temporary yet...?!" << endl;
return 0;
}


[snipped your code example, which used two compilation units]
Now, it is clear to see that when compiling main(), the compiler could not
have any clue that the temporary inside_t is used to initialize a const
reference. Therefore, it will destroy it after that line.
Agree.

Also, I have verified this behavior experimentally with MSVC. I more or
less assumed that the behavior was correct, and that MSVC compiled it
correctly - the "is this legal" question was more rhetorical than anything.

What I'm really interested in is *why* this is legal.

There's nothing wrong about passing a temporary as a reference
parameter. Consider expression evaluation, where temporaries are passed
by reference all the time.

How can the compiler guess that the programmer really wanted to bind
something else to the temporary using the parameter, and forgot it's the
parameter's lifetime that counts, and not the lifetime of that something
else?
 
V

Victor Bazarov

Benoit Mathieu said:
I'll check this in my Stroustrup tomorrow...


I would formulate the rule like this:
******
When you build a reference with a temporary object (like
Object & o = Object(...); )
the lifetime of Object is the same as the lifetime of the
reference o.

This is incorrect. You cannot bind a terporary to a non-const reference.
This is not allowed by the Standard and compliant compilers should complain.
*******
Claudio Jolowicz has another phrase to express this rule, if
you prefer... (18 Apr 2004 21:38:38 +0000 (UTC))

It has nothing to do with what I prefer. It's mandated by the Standard.
 
B

Benoit Mathieu

Victor said:
So, binding a temporary _directly_ to a const reference is what causes
the temporary to live as long as the reference

I'll check this in my Stroustrup tomorrow...
however, preserving
that const reference by initialising a different const reference with
it is NOT OK, since the lifetime of the first reference could be shorter
than that of the different const reference. Did I understand the gist
of your example correctly?

I would formulate the rule like this:
******
When you build a reference with a temporary object (like
Object & o = Object(...); )
the lifetime of Object is the same as the lifetime of the
reference o.
*******
Claudio Jolowicz has another phrase to express this rule, if
you prefer... (18 Apr 2004 21:38:38 +0000 (UTC))

This rule can be applied to the function call :
// Declaration of function foo:
foo(const Object & );

foo(Object(...));

Here we build a temporary Object(...), we build a reference
with it, which is given to function foo. The lifetime of the
function arguments is until the function's return. So the
lifetime of the temporary Object(...) which was directely
assigned to an argument is exactely the same.

So this unique rule explains both why outside_t instance was
not destroyed before "return 0" and why inside_t instance
was destroyed immediately after outside_t(const inside_t &)
constructor return in the OP's example.
Here is a different illustration of the same problem:

#include <iostream>
using namespace std;

struct A
{
char n;
A(char n) : n(n) { cout << "A(" << n << ")\n"; }
~A() { cout << "~A(" << n << ")\n"; }
};

void foo(const A& ra)
{
static const A& a = ra; // preserve the reference
static const A& aa = A('2'); // initialise another reference
}

int main()
{
foo(A('1'));
cout << "before end\n";
return 0;
}

You're right: this code is wrong.
We apply the rule : Instance A('1') is destroyed when the
reference it is assigned to is destroyed, which is when foo
returns.
Instance A('2') is destroyed when static const A& is
destroyed, that is at the end of program execution.
 
B

Benoit Mathieu

Object & o = Object(...); )
This is incorrect. You cannot bind a terporary to a non-const reference.
This is not allowed by the Standard and compliant compilers should complain.

Sorry, thanks for this rectification...

Benoit
 
M

Michael Sparks

Claudio Jolowicz said:
There's nothing wrong about passing a temporary as a reference
parameter. Consider expression evaluation, where temporaries are passed
by reference all the time.

How can the compiler guess that the programmer really wanted to bind
something else to the temporary using the parameter, and forgot it's the
parameter's lifetime that counts, and not the lifetime of that something
else?

I suppose I was just disappointed that the language allows this to happen,
due to its design.

It's apparent that the intent with references was to relieve some of the
responsibility the programmer has when dealing with pointers.
E.g., you don't have to check for NULL, and (usually) you don't have to
worry about them pointing to invalid places.

The language makes it pretty difficult in general to make a bad reference,
outside of writing *NULL.

Perhaps it was unavoidable for compatibility reasons, but this seems to me
like a blemish on an otherwise valuable language feature. In other words,
it's another thing to add to the long list of "things that might bite you in
the ass".

Note to anyone in this group with obsessive/compulsive disorder, and/or any
ego malfunctions: I'm not saying I could have done any better. :)

Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,522
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top