Temporaries - when do they begin, when do they end?

A

Asfand Yar Qazi

Sorry about this, but its driving me up the wall.

First some code:

typedef unsigned int size_t;

struct AddOp
{
template<class I1, class I2> static inline float
call(size_t i, const I1& i1, const I2& i2)
{
return i1 + i2;
}
};

struct MultOp
{
template<class I1, class I2> static inline float
call(size_t i, const I1& i1, const I2& i2)
{
return i1 * i2;
}
};

class Expr
{
float data;
public:
Expr(float arg_data) : data(arg_data) {}

float
operator[](size_t i) const {return data;}

};

template<class I1, class I2, class Op>
class Expr3
{
const I1& i1;
const I2& i2;
public:
Expr3(const I1& ai1, const I2& ai2)
: i1(ai1), i2(ai2)
{
}

float
operator[](size_t i) const
{
return Op::call(i, i1, i2);
}

template<class E>
Expr3<Expr3, E, AddOp>
operator+(const E& e) const
{
return Expr3<Expr3, E, AddOp>(*this, e);
}

};

struct Vec4
{
Vec4(float all)
: x(all), y(all), z(all), w(all)
{
}

Vec4(float ax, float ay, float az, float aw = 1)
: x(ax), y(ay), z(az), w(aw)
{
}

float x, y, z, w;

float
operator[](size_t i) const
{
switch(i) {
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
default: return 0xdeadbeef;
}

// HACKHACKHACK :)
// return (&x)
}

const Vec4&
operator=(float arg)
{
x = y = z = w = arg; return *this;
}

Expr3<Vec4, Expr, MultOp>
operator*(float e) const
{
// ****
return Expr3<Vec4, Expr, MultOp>(*this, Expr(e));
}

template<class E>
const Vec4&
operator=(const E& e)
{
x = e[0]; y = e[1]; z = e[2]; w = e[3]; return *this;
}
};

template<class T>
Expr3<Expr3<Vec4, Expr, MultOp>, Expr3<Vec4, Expr, MultOp>, AddOp>
dostuff(const T& arg)
{
return (arg * 0.9999f) + (arg * 0.00001f);
}

int
main()
{
const float ARG1 = 3.40282347e+38f;

Vec4 v1(ARG1), v2(ARG1);

v2 = dostuff(v1);
}


As you may have guessed its a slightly less than naive attempt at
doing a template expressions 3D vector maths library. (The above code
is just a test case composed of parts of the library.)

Here's the thing though: I get crashes when I run this. A reliable
source tell me that "The life time of Expr(e) is just this statement
so this is invalid." (referring to line underneath the ****)

I think I have to make the following modifications:

struct Vec4
{
...

Expr3<Vec4, Expr, MultOp>
operator*(Expr e) const
{
// ****
return Expr3<Vec4, Expr, MultOp>(*this, e);
}

...
};

// Get rid of dostuff method

....

int
main()
{
const float ARG1 = 3.40282347e+38f;

Vec4 v1(ARG1), v2(ARG1);

v2 = (v1 * Expr(0.9999f)) + (v1 * Expr(0.00001f));
}


I don't understand why this change should fix the program. I don't
understand why other temporaries don't end up crashing the program.
For example the temporary generated with the line "return Expr3<Vec4,
Expr, MultOp>(*this, e)"

Could someone clear this up for me? I'm really lost.

Also, could someone give me any critique as to any fundamental design
flaws of the code above? Or any other errors or undesirable code they
see?

Thanks,
Asfand Yar
 
J

Jonan

The reason for the problem is here (if I didn't got lost in the source):
template<class I1, class I2, class Op>
class Expr3
{
const I1& i1;
const I2& i2;
public:
Expr3(const I1& ai1, const I2& ai2)
: i1(ai1), i2(ai2)
{
}

You're passing references to the consturctor. Reference means pointer. in
your function your passing a pointer to something that gets killed on
function exit. While the next statement:
our pass
Expr3<Vec4, Expr, MultOp>
operator*(Expr e) const
{
// ****
return Expr3<Vec4, Expr, MultOp>(*this, e);
}

Is passing an object - not a reference. If you make the argument to be of
type "Expr&" - you'll have the same error.
-Jonan
 
A

Asfand Yar Qazi

Jonan said:
The reason for the problem is here (if I didn't got lost in the source):



You're passing references to the consturctor. Reference means pointer. in
your function your passing a pointer to something that gets killed on
function exit. While the next statement:




Is passing an object - not a reference. If you make the argument to be of
type "Expr&" - you'll have the same error.
-Jonan

So I can return a created temporary and it'll handle correctly?

But if I pass a temporary to a constructor, then when the scope in
which the temporary was created exits, that temporary will be
destroyed and I'll have a dangling pointer?

Man, this is confusing! :)
 
J

Jonan

So I can return a created temporary and it'll handle correctly?

But if I pass a temporary to a constructor, then when the scope in
which the temporary was created exits, that temporary will be
destroyed and I'll have a dangling pointer?

Man, this is confusing! :)


Not that confusing. See how it goes from the line that makes an error:
// ****
return Expr3<Vec4, Expr, MultOp>(*this, Expr(e));

This makes a temporary object Expr(e) that will die after this line is
completed - i.e. even when the code reaches "}" this object will be dead -
this is strictly speaking.

Upon that call the execution goes here:
Expr3(const I1& ai1, const I2& ai2)
: i1(ai1), i2(ai2)

The constructor accepts references - i.e. addresses to the objects passed.
This is still not that bad but - see:
const I1& i1;
const I2& i2;

They are both references. So You'll keep a reference to an object that will
be killed by the end of execution of line that is making the problem.
I hope this is more clear.

About returning the temporary - you're not returning a temporary, but that's
a whole another story.
See:
Expr3<Vec4, Expr, MultOp>
operator*(Expr e) const
{
// ****
return Expr3<Vec4, Expr, MultOp>(*this, e);
}

The return type is not a reference, so you create a temporary inside the
function, than you're returning the whole object back - not just a
reference. That's why it is ok.

Both question address to different issues - the warning is because you're
passing a reference to Expr(e) - in the second version you're passing that
object directly to the function so it is not a temporary and that's why the
warning is not there.
On the other side on both versions you don't have problem with returning
since both versions of operator* return object, not a reference.
Hope this helps.
-Jonan
 
A

Asfand Yar Qazi

Jonan said:
About returning the temporary - you're not returning a temporary, but that's
a whole another story.
See:




The return type is not a reference, so you create a temporary inside the
function, than you're returning the whole object back - not just a
reference. That's why it is ok.

Both question address to different issues - the warning is because you're
passing a reference to Expr(e) - in the second version you're passing that
object directly to the function so it is not a temporary and that's why the
warning is not there.
On the other side on both versions you don't have problem with returning
since both versions of operator* return object, not a reference.
Hope this helps.
-Jonan

Ah, I see. So when the + operator is called on the Expr3<Vec4, Expr,
MultOp> object, with the other Expr3<Vec4, Expr, MultOp> as argument,
a new temporary is created Expr3< Expr3<Vec4, Expr, MultOp>,
Expr3<Vec4, Expr, MultOp>, AddOp> . All references in all the
temporaries are valid, as the objects the reference were instansiated
in the arithmetic expression itself, not within one of the operator
calls, so are valid until the expression's end.

I understand now :)

Many thanks.

Thanks to your help, I now have a VERY fast framework for
component-wise arithmetic expression template code (beats uBlas and
valarray by orders of magnitude.)
 

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,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top