lifetime of temporaries

J

John Harrison

Rather a long program I'm afraid but I don't think I can cut it down any
further.

What I'm trying to do is construct a complex object Y from several X objects
in a complex expression. I'm trying to do this without creating any
temporaries of Y. To do that I'm defined a number of proxy classes which
contain references to the arguments in the expression. All the proxies
define a conversion operator to Y which will ultimately be used to create
the Y object. The issue for me is whether I'm guaranteed that all the
temporaries will still exist when the conversion happens. Here's the code

#include <iostream>
using namespace std;

int gen = 0;

class X
{
};

class Y
{
};

class Proxy
{
public:
Proxy() : id(gen++) {}
Proxy(const Proxy&) : id(gen++) {}
virtual ~Proxy() {}
operator Y()
{
cout << "convert\n";
return Y();
}
protected:
int id;
};

class XX_proxy : public Proxy
{
public:
XX_proxy(const X& l, const X& r) : left(l), right(r) { cout << "XX" << id
<< '\n'; }
~XX_proxy() { cout << "~XX" << id << '\n'; }
private:
const X& left;
const X& right;
};

class AX_proxy : public Proxy
{
public:
AX_proxy(const Proxy& l, const X& r) : left(l), right(r) { cout << "AX" <<
id << '\n'; }
~AX_proxy() { cout << "~AX" << id << '\n'; }
private:
const Proxy& left;
const X& right;
};

class XA_proxy : public Proxy
{
public:
XA_proxy(const X& l, const Proxy& r) : left(l), right(r) { cout << "XA" <<
id << '\n'; }
~XA_proxy() { cout << "~XA" << id << '\n'; }
private:
const X& left;
const Proxy& right;
};

class AA_proxy : public Proxy
{
public:
AA_proxy(const Proxy& l, const Proxy& r) : left(l), right(r) { cout << "AA"
<< id << '\n'; }
~AA_proxy() { cout << "~AA" << id << '\n'; }
private:
const Proxy& left;
const Proxy& right;
};

XX_proxy operator|(const X& l, const X& r)
{
return XX_proxy(l, r);
}

AX_proxy operator|(const Proxy& l, const X& r)
{
return AX_proxy(l, r);
}

XA_proxy operator|(const X& l, const Proxy& r)
{
return XA_proxy(l, r);
}

AA_proxy operator|(const Proxy& l, const Proxy& r)
{
return AA_proxy(l, r);
}

int main()
{
X x1, x2, x3, x4;
Y y = x1 | x2 | x3 | x4;
}

The following program outputs

XX0
AX1
AX2
convert
~AX2
~AX1
~XX0

on the two compilers I've tested it on, which is good, "convert" is output
before any of the destructors are called. But I'm not sure if this is
guaranteed behaviour. I've tried reading the standard but its a bit opaque
to me.

Thanks,
John
 
R

Rob Williscroft

John Harrison wrote in in comp.lang.c++:
What I'm trying to do is construct a complex object Y from several X
objects in a complex expression. I'm trying to do this without
creating any temporaries of Y. To do that I'm defined a number of
proxy classes which contain references to the arguments in the
expression. All the proxies define a conversion operator to Y which
will ultimately be used to create the Y object. The issue for me is
whether I'm guaranteed that all the temporaries will still exist when
the conversion happens. Here's the code

[snip expression template (without the template) example]

The temporaries are destroyed at the final sequence point of the
full expresion, that's the ';', after the initialization of Y y,
so your code is ok.

What will happen when/if we get the auto extension and can rewrite:
int main()
{
X x1, x2, x3, x 4;
Y y = x1 | x2 | x3 | x4;
}

as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.

Rob.
 
J

Janusz Szpilewski

Rob said:
What will happen when/if we get the auto extension and can rewrite:




as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.

According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler is used.


Regards,
Janusz
 
J

John Harrison

Janusz Szpilewski said:
According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler is used.


Regards,
Janusz

I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.

john
 
R

Rob Williscroft

Janusz Szpilewski wrote in in comp.lang.c++:
According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler
is used.

I wish you were right:

#include <iostream>
#include <ostream>

using namespace std;

struct X
{
X() { cout << "X()\n"; }
X( X const & ) { cout << "X(X const &)\n"; }
~X() { cout << "~X()\n"; }
};


struct Y
{
Y( X const &x ) : xp( &x ) { cout << "Y()\n"; }
Y( Y const &rhs ) : xp( rhs.xp ) { cout << "Y(Y const &)\n"; }
~Y() { cout << "~Y()\n"; }

X const *xp;
};


void f()
{
Y y( (X()) );

cout << "f()\n";
}

int main()
{
f();
cout.flush();
}


Every compiler I tried gave:

X()
Y()
~X()
f()
~Y()

So either they are all wrong (*), or that "...initializing an object..."
stuff isn't about keeping the X() from Y y( (X()) ); around.

*) Not impossible, compilers were:

MSVC 7.1, g++ 3.4 (prerelease) and CBuildeX (preview/EDG))

Consider:

X x1 = X(), x2 = X();

Note that the first X() gets consumed ( in effect becomes x1 )
by copy-ctor elision, so doesn't survive to the ';', but this is
2 initialization's, is it 1 (full) expression ?

So I really don't get that "initializing an object" stuff. All
I can think is it means when a temporary becomes the object
its initializing, so in effect the X() initializing x1 above
becomes x1, and thus 'lives' beyond the full expression.

Rob.
 
J

Janusz Szpilewski

Rob said:
So I really don't get that "initializing an object" stuff. All
I can think is it means when a temporary becomes the object
its initializing, so in effect the X() initializing x1 above
becomes x1, and thus 'lives' beyond the full expression.

Some qoute from the standard (draft version):

" (..) The first context is when an expression appears as an initializer
for a declarator defining an object. In that context, the temporary
that holds the result of the expression shall persist until the object's
initialization is complete. "

It means that the temporary should be destroyed after the object
initialization, hence just after leaving the constructor. So it does not
have to live as long as the constructed object.

Regards,
Janusz
 
J

Janusz Szpilewski

John said:
I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.

Yes, if a temporary is bound to a reference member it will persist only
until constructor ends.
 
J

John Harrison

Janusz Szpilewski said:
Yes, if a temporary is bound to a reference member it will persist only
until constructor ends.

But doesn't that apply to my other code as well? In that code I created an
XX_proxy object which was bound to a reference member (in a AX_proxy object)
yet that persisted beyond the end of the AX_proxy constructor. This is
essentially the point that confused me when I tried to read the standard.

John
 
J

Janusz Szpilewski

John said:
But doesn't that apply to my other code as well? In that code I created an
XX_proxy object which was bound to a reference member (in a AX_proxy object)
yet that persisted beyond the end of the AX_proxy constructor. This is
essentially the point that confused me when I tried to read the standard.

John

In the code posted previously the temporaries primarily existed within
the context of the full-expression where they were created (y = x1 | x2
| x3 | x4). In the latter case when 'func' returns the Z member
reference seems to be the only contexts of the existence of the
temporary what is not enough after the completion of the object
construction.

Regards,
Janusz
 
R

Rob Williscroft

Janusz Szpilewski wrote in in comp.lang.c++:
Some qoute from the standard (draft version):

" (..) The first context is when an expression appears as an initializer
for a declarator defining an object. In that context, the temporary
that holds the result of the expression shall persist until the object's
initialization is complete. "

It means that the temporary should be destroyed after the object
initialization, hence just after leaving the constructor. So it does not
have to live as long as the constructed object.

Yup, but that doesn't explain how, due to a temporary being
used in an intialization, the temporary can persist beyond the
full expression.

However I just searched the current Standard for 'temporary'
and I couldn't find a reference to:
According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

or similar, except for binding to T const & and in throw expression's.

So it seems my memory is at fault and temporaries are destroyed at
the end of "the full expression", except in the two cases noted
above.



Rob.
 
D

Dave Moore

John Harrison said:
Rather a long program I'm afraid but I don't think I can cut it down any
further.

*snip*

Y y = x1 | x2 | x3 | x4;

Well, fortunately the answer is short .. <grin> Yes, the temporaries
returned by the operator| calls are guaranteed to live until the end
of the full expression where they are generated .. in this case the
assignment expression quoted above.

Dave Moore
 
J

Janusz Szpilewski

Rob said:
Yup, but that doesn't explain how, due to a temporary being
used in an intialization, the temporary can persist beyond the
full expression.

However I just searched the current Standard for 'temporary'
and I couldn't find a reference to:




or similar, except for binding to T const & and in throw expression's.

So it seems my memory is at fault and temporaries are destroyed at
the end of "the full expression", except in the two cases noted
above.

When I wrote "longer" I primarily kept in mind the case of binding
temporary to a reference. Object initialization concerns more formal
issue as the formal name of an construct defining an object (containing
a type name) is called declarator and it covers more than just an
expression (which represents the initializer part). So the standard had
just to clarify this issue.

Regards,
Janusz
 
C

Cy Edmunds

John Harrison said:
Rather a long program I'm afraid but I don't think I can cut it down any
further.

What I'm trying to do is construct a complex object Y from several X objects
in a complex expression. I'm trying to do this without creating any
temporaries of Y. To do that I'm defined a number of proxy classes which
contain references to the arguments in the expression.

[snip]

I hope your objective of avoiding temporaries is worth writing code which
does so little with so much complexity. To put it more bluntly, I can hardly
think of a practical application where the increased efficiency would
justify such obscure code.

Still, it would be nice to be able to control creation and destruction of
temporaries. Maybe it's a problem with the language.
 
J

Jerald Fijerald

John Harrison said:
I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.

I think that's special because RVO should be applied here:
there is only one Z object with lifetime geater than func().
It's like saying

X x1, x2;
Z z (Y(x1), Y(x2));

where, too, the Y temporaries get destroyed while the Z still exists.

Gerald
 

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

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top