Sign in to follow this  
jinuuchukuu

Arrays with Structs and constructors !?

Recommended Posts

It seems I went writing this same threat in the bad folder: For beginners… So I retry my chance here if somebody could help me with a typedef struct I took from a tutorial, having thought I could add another struct containing a constructor too, to ease the operations on those packages of data…the package is not so big, but it could be it fast… So here they are:
typedef struct tVector3
{			
	tVector3() {}	// constructor
	tVector3 (float new_x, float new_y, float new_z) // initialize constructor	 
	{x = new_x; y = new_y; z = new_z;}
	tVector3 operator+(tVector3 vVector) {return tVector3(vVector.x+x, vVector.y+y, vVector.z+z);}
	tVector3 operator-(tVector3 vVector) {return tVector3(x-vVector.x, y-vVector.y, z-vVector.z);}
	tVector3 operator*(float number)	 {return tVector3(x*number, y*number, z*number);}
	tVector3 operator/(float number)	 {return tVector3(x/number, y/number, z/number);}
	
	float x, y, z;
}tVector3;

typedef struct tPlan2
{
	tPlan2() {}
	tPlan2(tVector3 new_a, tVector3 new_b)
	{a= new_a; b=new_b;}
	tPlan2 operator+(tPlan2 vPlan) {return tPlan2(vPlan.a+a, vPlan.b+b);}
	tPlan2 operator-(tPlan2 vPlan) {return tPlan2(a-vPlan.a, b-vPlan.b);}
	tPlan2 operator*(float num) {return tPlan2(num*a, num*b);}
	tPlan2 operator/(float num) {return tPlan2(a/num, b/num);}

	tVector3	a;
	tVector3	b;
}tPlan2;

typedef struct tPlan4
{
	tPlan4() {}
	tPlan4(tVector3 new_a, tVector3 new_b, tVector3 new_c, tVector3 new_d)
	{a=new_a; b=new_b; c=new_c; d=new_d;}
	tPlan4 operator+(tPlan4 vPlan) {return tPlan4(vPlan.a+a, vPlan.b+b, vPlan.c+c, vPlan.d+d);}
	tPlan4 operator-(tPlan4 vPlan) {return tPlan4(vPlan.a-a, vPlan.b-b, vPlan.c-c, vPlan.d-d);}
	tPlan4 operator*(float num) {return tPlan4(num*a, num*b, num*c, num*d);}
	tPlan4 operator/(float num) {return tPlan4(a/num, b/num, c/num, d/num);}

	tVector3	a;
	tVector3	b;
	tVector3	c;
	tVector3	d;
}tPlan4;

Pretty please, how could I retrieve a usable form from these structures to avoid any error from the compiler ?! Which are: error C2677: binary '*' : no global operator defined which takes type 'struct tVector3' (or there is no acceptable conversion) all for the lines only concerning * operator in tPlan4 or tPlan2…?! Someone somebody some spirit ? After that most important if that somewhat is known how to declare and use arrays with that type of typedefStruct pleaseYourself !!
edit by k2: added source tags [Edited by - kSquared on October 22, 2005 7:49:49 AM]

Share this post


Link to post
Share on other sites
You haven't defined a '*' operator to take, respectively, a float then a tVector3, but you have an operator '*' defined for tVector3 that takes a float as its argument. You just need to replace all instances of "num*x" with "x*num", where x is any tVector3.

Also, if this is C++, you don't need the typedefs.

EDIT: So something like this:
struct tVector3
{
tVector3() {} // constructor
tVector3 (float new_x, float new_y, float new_z) // initialize constructor
{x = new_x; y = new_y; z = new_z;}
tVector3 operator+(tVector3 vVector) {return tVector3(vVector.x+x, vVector.y+y, vVector.z+z);}
tVector3 operator-(tVector3 vVector) {return tVector3(x-vVector.x, y-vVector.y, z-vVector.z);}
tVector3 operator*(float number) {return tVector3(x*number, y*number, z*number);}
tVector3 operator/(float number) {return tVector3(x/number, y/number, z/number);}

float x, y, z;
};

struct tPlan2
{
tPlan2() {}
tPlan2(tVector3 new_a, tVector3 new_b)
{a= new_a; b=new_b;}
tPlan2 operator+(tPlan2 vPlan) {return tPlan2(vPlan.a+a, vPlan.b+b);}
tPlan2 operator-(tPlan2 vPlan) {return tPlan2(a-vPlan.a, b-vPlan.b);}
tPlan2 operator*(float num) {return tPlan2(a*num, b*num);}
tPlan2 operator/(float num) {return tPlan2(a/num, b/num);}

tVector3 a;
tVector3 b;
};

struct tPlan4
{
tPlan4() {}
tPlan4(tVector3 new_a, tVector3 new_b, tVector3 new_c, tVector3 new_d)
{a=new_a; b=new_b; c=new_c; d=new_d;}
tPlan4 operator+(tPlan4 vPlan) {return tPlan4(vPlan.a+a, vPlan.b+b, vPlan.c+c, vPlan.d+d);}
tPlan4 operator-(tPlan4 vPlan) {return tPlan4(vPlan.a-a, vPlan.b-b, vPlan.c-c, vPlan.d-d);}
tPlan4 operator*(float num) {return tPlan4(a*num, b*num, c*num, d*num);}
tPlan4 operator/(float num) {return tPlan4(a/num, b/num, c/num, d/num);}

tVector3 a;
tVector3 b;
tVector3 c;
tVector3 d;
};

Share this post


Link to post
Share on other sites
More generally, so that you do not fall foul of this problem when you use operator* in other code, you should implement a non-member operator* which takes a first parameter of type float and a second parameter of type tPlan2 or tPlan4 as appropriate.

Additionally, you should add const qualification to your operators and I would advise making the existing versions non member as well, since there is no need for them to be members:
struct Vector3
{

Vector3()
{
}

Vector3(float new_x, float new_y, float new_z)
:
x(new_x),
y(new_y),
z(new_z)
{
}

float x, y, z;

};

Vector3 operator+(Vector3 const & lhs, Vector3 const & rhs)
{
return Vector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
}

Vector3 operator-(Vector3 const & lhs, Vector3 const & rhs)
{
return Vector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
}

Vector3 operator*(Vector3 const & vector, float multiplier)
{
return Vector3(vector.x * multiplier, vector.y * multiplier, vector.z * multiplier);
}

Vector3 operator*(float multiplier, Vector3 const & vector)
{
return vector * multiplier;
}

Vector3 operator/(Vector3 const & vector, float divisor)
{
return Vector3(vector.x / divisor, vector.y / divisor, vector.z / divisor);
}

struct Plan2 // Plane2 or PlanePointNormal?
{

Plan2()
{
}

Plan2(Vector3 new_a, Vector3 new_b) // use better names
:
a(new_a),
b(new_b)
{
}

Vector3 a;
Vector3 b;

};

Plan2 operator+(Plan2 const & lhs, Plan2 const & rhs)
{
return Plan2(lhs.a + rhs.a, lhs.b + rhs.b);
}

Plan2 operator-(Plan2 const & lhs, Plan2 const & rhs)
{
return Plan2(lhs.a - rhs.a, lhs.b - rhs.b);
}

Plan2 operator*(Plan2 const & plan, float multiplier)
{
return Plan2(plan.a * multiplier, plan.b * multiplier);
}

Plan2 operator*(float multiplier, Plan2 const & plan)
{
return plan * multiplier;
}

Plan2 operator/(Plan2 const & plan, float divisor)
{
return Plan2(plan.a / divisor, plan.b / divisor);
}

struct Plan3 // Plane3 or PlaneCartesian?
{

Plan3()
{
}

Plan3(Vector3 new_a, Vector3 new_b, Vector3 new_c, Vector3 new_d) // use better names
:
a(new_a),
b(new_b),
c(new_c),
d(new_d)
{
}

Vector3 a;
Vector3 b;
Vector3 c;
Vector3 d;

};

Plan3 operator+(Plan3 const & lhs, Plan3 const & rhs)
{
return Plan3(lhs.a + rhs.a, lhs.b + rhs.b, lhs.c + rhs.c, lhs.d + rhs.d);
}

Plan3 operator-(Plan3 const & lhs, Plan3 const & rhs)
{
return Plan3(lhs.a - rhs.a, lhs.b - rhs.b, lhs.c - rhs.c, lhs.d - rhs.d);
}

Plan3 operator*(Plan3 const & plan, float multiplier)
{
return Plan3(plan.a * multiplier, plan.b * multiplier, plan.c * multiplier, plan.d * multiplier);
}

Plan3 operator*(float multiplier, Plan3 const & plan)
{
return plan * multiplier;
}

Plan3 operator/(Plan3 const & plan, float divisor)
{
return Plan3(plan.a / divisor, plan.b / divisor, plan.c / divisor, plan.d / divisor);
}

It may also be an idea to implement operator+=, operator-=, operator*= and operator/= and implement your current operators in terms of those, i.e.:
Vector3 & operator+=(Vector3 & lhs, Vector3 const & rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
return lhs;
}

Vector3 operator+(Vector3 lhs, Vector3 const & rhs)
{
return lhs += rhs;
}

Enigma

Share this post


Link to post
Share on other sites
Mostly consistency, but also (a very small part) future-proofing.

Consistency for two reasons. First, operatorX(Non-ClassInstance, ClassInstance) must be a non-member and therefore for consistency the other operators should be non-members as well. Secondly any class that has an implicit type conversion from X (i.e. any class with a non-explicit constructor taking at least one parameter for which all or all but the first parameter have default values) will need to have non-member operators in order to gain type conversion of the left hand object:
struct OtherType
{
};

struct Type
{

Type();
Type(OtherType const & ot);
Type operator+(Type const & t) const; // member operator

};

Type operator-(Type const & lhs, Type const & rhs); // non-member operator

int main()
{
Type t1;
OtherType ot1;
Type t2 = t1 + ot1; // fine - type conversion on parameter
Type t3 = ot1 + t1; // error - no type conversion possible on "this"
Type t4 = t1 - ot1; // fine - type conversion on second parameter
Type t5 = ot1 - t1; // fine - type conversion on first parameter
}

Since classes which require implicit type conversion on the left hand object must have non-member operators all classes should have non-member operators for consistency.

Finally, the future proofing leads on from the last point. If the class were ever to gain a type conversion from X then it's operators will need to be non-members. There is obviously less maintenance to do if the operators are already non-members.

Enigma

Share this post


Link to post
Share on other sites
Everything went super well! For sure it was a sure thing ;-) It works as on wheels, but as the truncated f numbers that dig you intervals of mistakes, the unknown of those quadric parentheses welcomes the times of “arrays uses” seeking and tries…with that kind of structure.
I thought about something like this :
struct zen{
The wind flies, the water wets, the fire burns;

No idea of how;
}


To be continued...

Share this post


Link to post
Share on other sites
Quote:
Original post by Enigma
Mostly consistency, but also (a very small part) future-proofing.

Consistency for two reasons. First, operatorX(Non-ClassInstance, ClassInstance) must be a non-member and therefore for consistency the other operators should be non-members as well. Secondly any class that has an implicit type conversion from X (i.e. any class with a non-explicit constructor taking at least one parameter for which all or all but the first parameter have default values) will need to have non-member operators in order to gain type conversion of the left hand object:
*** Source Snippet Removed ***
Since classes which require implicit type conversion on the left hand object must have non-member operators all classes should have non-member operators for consistency.

Finally, the future proofing leads on from the last point. If the class were ever to gain a type conversion from X then it's operators will need to be non-members. There is obviously less maintenance to do if the operators are already non-members.

Enigma




I see, but why does hardly anyone make those operators non member?
Even in 99 C++ gotchas they make operators members of the class which sounds seems logical to me, because you encapsulate the implementation into the type which makes maintainance a lot easier if you switch class types later on in your project
you just make sure the Class-Instance Class-Instance operators fullfil the specification and you are fine
only the none-Class-Instance operators need to be changes

Share this post


Link to post
Share on other sites
You didn’t write for nothing enigma if you counted to be understood…
After revising my pointers and references I got it fine ‘bout the type conversion.

Thanks Basiror for asking ‘bout the non-member, I wouldn’t ask until the next error!

The “opened” construction of operators making possible the conversion of data between references… but as usual? I mean as a simple conversion between an integer and a float for example, following the same rules?
Conserving the required use of references and pointers I see…

Share this post


Link to post
Share on other sites
Quote:
Original post by Basiror
I see, but why does hardly anyone make those operators non member?

Because Enigma's approach requires that your class/struct have public member variables, which (generally) contradicts encapsulation and data hiding. For a simple numerical type like a vector, his approach may be adequate, but not for more complex types with internal representations requiring a high degree of numerical stability. Exposing those publicly almost invites (dumber) programmers to directly access the values.

Of course, property-like accessors could ameliorate the problem somewhat.

Share this post


Link to post
Share on other sites
In response to Oluseyi's point I would point out that although I didn't state so explicitly (and probably should have done) what I wrote only fully applies to operatorX and not operatorX=. Since operatorX= can never require an implicit type conversion on the left hand operand or have a left hand operand of non-class type operatorX= can safely be made a member and operatorX a non-member implemented in terms of operatorX=.

You may feel that breaks the point I was making about consistancy, but the real meat of the point is that if you always implement operatorX as a non-member it will never be a wrong choice. Equally if you always implement operatorX= as a member it will never be a wrong choice. The fact that I wrote a non-member operatorX= in my first post nonwithstanding (since that was mostly to save a few lines rather than for any deep reason).

Enigma

Share this post


Link to post
Share on other sites
An additional point on consistency:

For user-defined types, it is wise to emulate the functionality of built-in types where possible (this especially applies to types that can represent numbers). For operator -, consider the following:

tvector3 v1( 1, 2, 3 ), v2( 4, 5, 6 ), v3( v1-v2 );
...
if( v1-v2 = v3 ) // oops, meant to do a comparison (==)
...

The difference is subtle here, and most compilers will flag the conditional on tvector3 because of the (incorrect) attempted conversion to bool. For built-in types, this code of course would not compile. But say you have a rational number class, for instance, you'd probably want an implicit conversion to float, which as a built-in type has an implicit conversion to int, which has an implicit conversion to bool. In this case the above typo would compile and give you extremely erroneous results.

To solve this problem at compile-time, not only will you return an object by value from operator-(), but you will also make that return value const:

const rational operator -( const rational & );

This prevents debugging headaches by not allowing the "I meant conditional instead of assignnemt" to compile. This applies to all types implicitly convertible to int or bool, but again for consistency's sake, it's a good habit to get into.

Share this post


Link to post
Share on other sites
All right, I still get the main idea of a protected pack of data, and the external traces access of public ones through structures and classes…all that even made me think ‘bout a compression decompression program and his coding and decoding functions making more comfortable the inner call of each other structures in classes driven by the result of an another structure… Too much confusion in the rules of different declarations in classes although in structures to make an exploitable example right here…Like calibrating on the machine speed (clk tck) to have the most optimized chain of functions… still counting on errors to discover the rules

Beside, I’d really like to learn how to make one or several arrays from these structures we’re talking ‘bout…does anyone?

Share this post


Link to post
Share on other sites
Since your structs all have default constructors and the default copy assignment operator does the right thing you can declare and use an array of them just like you would any builtin type:
Vector3 stackArray[42];
int x = 42;
Vector3 * dynamicArray = new Vector3[x];
std::vector< Vector3 > saferDynamicArray(x);
stackArray[7] = dynamicArray[8] = saferDynamicArray[9] = Vector3(7, 8, 9);
delete[] dynamicArray;

Enigma

Share this post


Link to post
Share on other sites
Quote:
Original post by jinuuchukuu
After that most important if that somewhat is known how to declare and use arrays with that type of typedefStruct pleaseYourself !!

Not quite sure what you're asking, but you can create arrays of classes like this:

The hard way:

const unsigned int array_size = 1000;

// unsafe static array
tVector3 unsafe_static_array[ array_size ] = { tVector3() };

// unsafe 'dynamic' array
tVector3 * unsafe_dynamic_array = new tVector3[ array_size ];

The easy way:

#include <vector>

const unsigned int array_size = 1000;

// safe array that grows dynamically
std::vector< tVector3 > safe_array1;

// safe array with initial size that grows dynamically
std::vector< tVector3 > safe_array2( array_size, tVector3() );

EDIT: beaten.

Share this post


Link to post
Share on other sites
Not only I couldn’t stay to wait for an answer,(hope there will still be someone looking here) I couldn’t afford waiting to answer you before I have to try what you guys tell me… but in the compiler vc++ 6 none of the declarations seems to be token in consideration especially when I use
stackarraytPlanX[iter].a.x =
stackarraytPlanX[iter]->a.x =


Making those declarations in a class declaration in a header file…
WHAT HAPPENS! I never fall on good source text about that subject maybe because I don’t pass the beginner’s cap for some reason… does someone know about a great book for c++ with great explainations ?
Anyway,
Quote:
from StylinThe easy way:
#include <vector>
const unsigned int array_size = 1000;
// safe array that grows dynamically
std::vector< tVector3 > safe_array1;
// safe array with initial size that grows dynamically
std::vector< tVector3 > safe_array2( array_size, tVector3() );

i guess those safe ways doesn't work declared in classes...

Share this post


Link to post
Share on other sites
Quote:
Original post by jinuuchukuu
i guess those safe ways doesn't work declared in classes...

It might be a communication problem, but I have a hard time understanding what you are asking. The [] operator for a std::vector works like a standard array, ie., it returns a reference to the index you pass to it.

// meaningless shape class for demonstration
class shape { public: void draw() {;} };

// create a vector of 420 default shapes
std::vector< shape > shape( 420, shape() );

// access those shapes using operator[]
for( int i=0; i<shapes.size(); ++i )
shapes[i].draw(); // shapes[i] returns a reference to the ith element

// access those shapes using an iterator
std::vector< shape >::iterator it = shapes.begin();
while( it != shapes.end() ) {
it->draw(); // it points to a particular element
(*it).draw(); // so we can dereference the iterator as well
++it; // point to the next element
}

In your code, it looks like you're trying to pass an iterator to operator[]; if you have an iterator, you can just dereference it to gain access to the element it points to. To use [] notation, just pass an integer index (exactly like you do for arrays) and that will return a reference to that element.

Let me know if this is still unclear.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this