• Advertisement
Sign in to follow this  

any way to do this without the ugly macro?

This topic is 3984 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Okay, does anyone see how I can do this without the macro? (and writing the same thing everywhere i need it does not count!) tuple<T,N> is supposed to be the data holding base of future vector, quaternion etc. classes and since additions etc. are the same for all (and printing too) of them i wanted to implement these once and only implement special operations (for example, the dot product for) in the derived classes, but this is a bit problematic because the operators obviously return objects of type tuple<T,N> and these can't be trivially assigned to the derived classes. or can they and im doing it wrong? (it does work the way it is, but i would prefer not to have to resort to macros)
#ifndef CSE_TUPLE_H
#define CSE_TUPLE_H

#include "common.h"

#define CSE_TUPLE_ASSIGN_MACRO(THISTYPE, T, N) \ 
 	THISTYPE& operator=(const tuple<T,N>& o) \ 
 	{ memcpy(tuple<T,N>::data,&o,sizeof(tuple<T,N>)); return *this; } \ 
	THISTYPE(const tuple<T,N>& o) \ 
	{ memcpy(tuple<T,N>::data,&o,sizeof(tuple<T,N>)); }

/// Base template for all tuples (vectors, colors, quaternions, etc.)
template<class T, int N>
struct tuple
{
	/// Basic non-initializing constructor
	tuple() {}
	/*
	// as we just want to copy memory content, these shouldn't be necessary
	tuple(const tuple<T,N>& o)
	{
		memcpy(this,&o,sizeof(tuple<T,N>));
	}
	tuple<T,N>& operator=(const tuple<T,N>& o)
	{
		memcpy(this,&o,sizeof(tuple<T,N>));
		return *this;
	}
	*/

	/// Data access
	inline T& operator() (int i)
	{
		assert(i>=0&&i<N);
		return data;
	}

	/// Const data access
	const T& operator() (int i) const
	{
		assert(i>=0&&i<N);
		return data;
	}

	tuple<T,N> operator- () const
	{
		tuple<T,N> result = *this;
		for(int i = 0; i < N; ++i)
			result.data = -result.data;
		return result;
	}

	tuple<T,N> operator+ (const tuple<T,N>& o) const
	{
		tuple<T,N> result = *this;
		for(int i = 0; i < N; ++i)
			result.data += o.data;
		return result;
	}

	tuple<T,N>& operator+= (const tuple<T,N>& o)
	{
		for(int i = 0; i < N; ++i)
			data += o.data;
		return result;
	}

	T* getDataPtr()
	{
		return data;
	}

protected:
	T data[N];
};

/// Templated overload of << operator for printing via cout
template<class T,int N> std::ostream& operator<< (std::ostream& ostr, const tuple<T,N>& tup)
{
	ostr << '[';
	for(int i = 0; i < N; ++i)
	{
		ostr << tup(i);
		if(i != N-1)
			ostr << ", ";
	}
	return ostr << ']';
}

/// Template for casting between tuple data types, usage just like static_cast
template<class T, class T2, int N>
tuple<T,N> tuple_cast(const tuple<T2,N>& o)
{
	tuple<T,N> result;
	for(int i = 0; i < N; ++i)
		result(i) = static_cast<T>(o(i));
	return result;
}

struct vecy : public tuple<int,2>
{
	CSE_TUPLE_ASSIGN_MACRO(vecy,int,2)
	vecy() {}
};

#endif


[Edited by - l0calh05t on March 23, 2007 6:22:26 AM]

Share this post


Link to post
Share on other sites
Advertisement
0) place spaces after the '\'s in the macro to stop it showing up as one line

1) use std::copy instead of memcpy to ensure that something like tuple<fixed_point, 3> calls fixed_point's copy constructor and assignment operator correctly.

2) Consider adding a begin() and end() member function to be more SC++L compatible

3) operator- and operator+ should be free functions.

Okay now im done being picky my suggestion is: typedef tuple<float, 3> vec;

Or if thats not flexible enough then:

template<typename T, int N, int ID>
struct unuiqe_type_tuple : tuple<T, N>
{
unuiqe_type_tuple<T, N, ID>& operator=(const tuple<T,N>& o)
{ tuple<T, N>::operator=(o); return *this; }

unuiqe_type_tuple<T, N, ID>(const tuple<T,N>& o) : tuple<T, N>(o)
{ }
};

enum
{
vec_id
};

typedef unuiqe_type_tuple<float, 3, vec_id> vec;




I really dont see the problem with using a macro though just make it a local macro (i.e. #undef it when your done) and the most prominant issue with macros disapears (i.e. that they dont respect scoping rules) beacause it will never interact with client code.

Share this post


Link to post
Share on other sites
There is no point overriding the default copy-constructor and assignment operator when all you do is a memcpy. If you don't specify these then it will generate ones that effectively do exactly the same thing, and possibly more efficiently too![cool]

Simply delete the macro, and everywhere it is used, and delete the default (empty) constructor as well. Problem solved![grin]

Your comment:
Quote:
// as we just want to copy memory content, these shouldn't be necessary
is actually correct; they aren't necessary.

Share this post


Link to post
Share on other sites
Quote:
Original post by iMalc
Problem solved![grin]


Except for the return types at any rate in this case ;-)

Quote:
Original post by l0calh05t
Okay, does anyone see how I can do this without the macro?

A pattern commonly known as CRTP, or the Curiously Reoccuring Template Pattern. Obtuse, I know. Basically, a class passes itself as a template parameter to a base class. See vertex's definition.

template < typename Self , typename T , size_t N >
struct arith_array_base {

Self& operator+=( const Self & other ) {
for ( unsigned i = 0 ; i < N ; ++i ) data += other.data;
return static_cast< Self& >( *this );
}
Self& operator-=( const Self & other ) {
for ( unsigned i = 0 ; i < N ; ++i ) data -= other.data;
return static_cast< Self& >( *this );
}

T& operator[]( size_t i ) { assert( i < N ); return data; }
const T& operator[]( size_t i ) const { assert( i < N ); return data; }

T* ptr() { return data; }
const T* ptr() const { return data; }

friend Self operator+( const Self & lhs , const Self & rhs ) { Self copy(lhs); copy += rhs; return copy; }
friend Self operator-( const Self & lhs , const Self & rhs ) { Self copy(lhs); copy -= rhs; return copy; }
protected:
T data[N];
};

struct vertex : arith_array_base< vertex , float , 3 > {};

int main() {
vertex v1,v2,v3;
v1 = v2 + v3;
}





Notes:

1) I always place the Self parameter first, to help make it clear when CRTP is being used.
2) "> 0" invariant mantained with signedness (allowing us to generate compile time warnings) rather than run time checks.
3) Verbose name I know, but it's closer to boost::array than boost::tuple, so I couldn't help changing it.
4) Crazy whitespacing I know, I'm just obsessed with lining up duplicated code so it's easily spotted, and pattern variations (accidental or intentional) made obvious.
5) Your current operator+= didn't work (there is no "result" local variable to be returned, and even if there were, since it returns a reference, that would've been horribly broken anyways.
6) It's easier to implement + in terms of +=, so I do so.
7) All functions listed inline to the class definition are implicitly of inline linkage, so I didn't bother explicitly repeating that.
8) Generally fiddled around into conforming to my own standards :P.

9) Although it was not involved here, copy-construction and assignment are special cases in that the arguments involved cannot simply be Self, as they will not override the autogenerated versions. Instead, if the members of Self were required, you would end up with something like:

arith_array_base( const arith_array_base & other_ ) {
const Self& other = static_cast< const Self& >( other_ );
...
}


10) I could've safely used arith_array_base as the argument types as well, since I don't use any members specific to Self in the base class, since each class that uses arith_array_base gets their own version (thanks to the Self parameter being unique to the class), no worries about a situation like this:

struct vertex2d : arith_array_base< vertex2d , float , 2 > {};
struct texcoord : arith_array_base< texcoord , float , 2 > {};

int main() {
vertex2d v;
texcoord t;
v = t;
}


error C2679: binary '=' : no operator found which takes a right-hand operand of type 'texcoord' (or there is no acceptable conversion)

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by iMalc
Problem solved![grin]


Except for the return types at any rate in this case ;-)

Quote:
Original post by l0calh05t
Okay, does anyone see how I can do this without the macro?

A pattern commonly known as CRTP, or the Curiously Reoccuring Template Pattern. Obtuse, I know. Basically, a class passes itself as a template parameter to a base class. See vertex's definition.
Oh, I had forgotten about that.

You know the scary thing is, I've actually used that method before (Just didn't know what it was called). However I later decided that it was better to switch to using a typedef instead, as the downside that I found of the above, is that you have to cast the 'this' pointer everywhere when returning it.

Sorry I hadn't noticed that it was this same issue earlier.

Share this post


Link to post
Share on other sites
Quote:
Original post by iMalc
You know the scary thing is, I've actually used that method before (Just didn't know what it was called). However I later decided that it was better to switch to using a typedef instead, as the downside that I found of the above, is that you have to cast the 'this' pointer everywhere when returning it.


e.g. typedef arith_array< float , 2 > vertex2d; ?

In my worse monstrosities, I found an inline "self()" function returning the casted *this to be of benifit, for accessing the members, functions, and so forth of the deriving class (as well as returns).

Share this post


Link to post
Share on other sites
Ok... let's deal with these one by one... (why didn't I get any notifications.. .grr...)

Quote:
Original post by Julian90
0) place spaces after the '\'s in the macro to stop it showing up as one line


Done

Quote:
1) use std::copy instead of memcpy to ensure that something like tuple<fixed_point, 3> calls fixed_point's copy constructor and assignment operator correctly.


As i didn't plan on using any non-base types (float, int etc. only) it would not really have mattered, but for correctness I guess that might be better.

Quote:
2) Consider adding a begin() and end() member function to be more SC++L compatible


Uh.. i'm not really shure what you mean, I guess i could include a begin and end member, but what for? (please clarify)

Quote:

3) operator- and operator+ should be free functions.


why?

Quote:

Okay now im done being picky my suggestion is: typedef tuple<float, 3> vec;


Most certainly not. The whole point is to have a base class with the basic functions (+, -, += etc.) and derived types like vector, or quaternion which implement specific functions like a dot product (vector) or toMatrix (quaternion). It would be pretty silly if I had a dot product for colors, would it not? (But in case I do need it somewhere {due to special circumstances, so ideally no implicit casting} I want a simple way of casting it to a vector of according type if for example the color represents a normal, as in a normal map)

Quote:

Or if thats not flexible enough then:
*** Source Snippet Removed ***

I really dont see the problem with using a macro though just make it a local macro (i.e. #undef it when your done) and the most prominant issue with macros disapears (i.e. that they dont respect scoping rules) beacause it will never interact with client code.


That source snippet doesn't really solve my problem. (see above)

#undef-ing the macro did occur to me but in that case I'd have to put all types into the one file, or, use #defines and #ifdefs to protect other files that include it and in the end that's even uglier than before...

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by iMalc
Problem solved![grin]


Except for the return types at any rate in this case ;-)


Yup.

Quote:
A pattern commonly known as CRTP, or the Curiously Reoccuring Template Pattern. Obtuse, I know. Basically, a class passes itself as a template parameter to a base class. See vertex's definition.

*** Source Snippet Removed ***


Hey, that's pretty much the idea I had just before going to sleep :-D
But will this work?


template<class T, int N, class Self = tuple> struct tuple
{
/* bla */
}



Because sometimes a tuple is just a plain and simple tuple.

And... can you think of an (ideally syntactically nice) way of providing explicit casting so that i can do something like (for the reasons stated in my previous post):


color4f col;
vector4f vec = tuple_type_cast<vector4f>(col); // T and N remain constant



Then again... I could also use *(reinterpret_cast<vector4f*>(&col))

Quote:

Notes:

1) I always place the Self parameter first, to help make it clear when CRTP is being used.
2) "> 0" invariant mantained with signedness (allowing us to generate compile time warnings) rather than run time checks.


You mean for the operator()? >eah, i could do that, perhaps size_t would be the most appropriate type.

Quote:
3) Verbose name I know, but it's closer to boost::array than boost::tuple, so I couldn't help changing it.


Well, in many cases you'll have an array of vectors, which - in component representation are tuples of real numbers - and not really an array of arrays imo. But then again that's only semantics and of little importance and mainly a question of personal preference.

Quote:
4) Crazy whitespacing I know, I'm just obsessed with lining up duplicated code so it's easily spotted, and pattern variations (accidental or intentional) made obvious.
5) Your current operator+= didn't work (there is no "result" local variable to be returned, and even if there were, since it returns a reference, that would've been horribly broken anyways.


Not as crazy as you think. Actually pretty smart.

And that's what you get for copy-and-pasting too much :-P (i just used my operator+() and removed all occurrences of result... or so i thought)

Quote:
6) It's easier to implement + in terms of +=, so I do so.
7) All functions listed inline to the class definition are implicitly of inline linkage, so I didn't bother explicitly repeating that.


I don't trust my compiler. (and the functions will probably end up in a separate inline header in the end anyways, something i picked up from 3DGEA)

Quote:
8) Generally fiddled around into conforming to my own standards :P.


Nothing wrong with that. I'm still working on defining mine. (Noticed i've been sticking to the somewhat microsoft "CSomethingUgly" style way too much)

Quote:
9) Although it was not involved here, copy-construction and assignment are special cases in that the arguments involved cannot simply be Self, as they will not override the autogenerated versions. Instead, if the members of Self were required, you would end up with something like:

arith_array_base( const arith_array_base & other_ ) {
const Self& other = static_cast< const Self& >( other_ );
...
}


Obviously, otherwise I wouldn't have gotten into the whole macro-mess in the first place (if operator= and the copy-constructor were passed over to the derived classes)

Quote:
10) I could've safely used arith_array_base as the argument types as well, since I don't use any members specific to Self in the base class, since each class that uses arith_array_base gets their own version (thanks to the Self parameter being unique to the class), no worries about a situation like this:

struct vertex2d : arith_array_base< vertex2d , float , 2 > {};
struct texcoord : arith_array_base< texcoord , float , 2 > {};

int main() {
vertex2d v;
texcoord t;
v = t;
}


error C2679: binary '=' : no operator found which takes a right-hand operand of type 'texcoord' (or there is no acceptable conversion)


Sorry, didn't quite understand this last part here (Mainly the first sentence) Are you just pointing out that each arith_array_base will be a different type (due to the extra template parameter)?

Share this post


Link to post
Share on other sites
Quote:
Original post by l0calh05t
But will this work?

*** Source Snippet Removed ***

Because sometimes a tuple is just a plain and simple tuple.


Apparently not.

First try:


template<class T, int N, class Self = tuple>
struct tuple
{
/* ... */
};



Real type expected, ok duh, have to give template parameters

Second try:


template<class T, int N, class Self = tuple<T,N> >
struct tuple
{
/* ... */
};



Fatal error C1202: recursive type or function dependency context too complex

Third try, ok, perhaps an additional specialization will do:


template<class T, int N, class Self = tuple<T,N> >
struct tuple
{
/* ... */
};

template<class T, int N>
struct tuple<T,N,tuple<T,N> >
{
/* ... */
};



Fatal error C1202: recursive type or function dependency context too complex

Ideas?

Share this post


Link to post
Share on other sites
Quote:
Original post by Julian90
0) place spaces after the '\'s in the macro to stop it showing up as one line


That used to not compile under gcc, don't know about whether it does now.
I'm fairly sure it is non-standard though, a \ should be the last character on the line I thought.

Share this post


Link to post
Share on other sites
Quote:
That used to not compile under gcc, don't know about whether it does now.
I'm fairly sure it is non-standard though, a \ should be the last character on the line I thought.


I'd hope it doesnt compile :), i meant in the forum post otherwise the forum processes out the escape sequences and puts the macro on one big line :(.

Quote:
Uh.. i'm not really shure what you mean, I guess i could include a begin and end member, but what for? (please clarify)


so you can do things like
std::transform(my_tuple.begin(), my_tuple.end(), my_ruple.begin, std::bind1st(std::plus(), 1));

Quote:
3) operator- and operator+ should be free functions.


Implementing them as member functions prevents the compiler from performing an implicit cast on the left operand and its more consistent with the semantics of addition, you dont want the first thing to add the second thing to itself you want the first and second thing to be added together.

Quote:
That source snippet doesn't really solve my problem. (see above)

#undef-ing the macro did occur to me but in that case I'd have to put all types into the one file, or, use #defines and #ifdefs to protect other files that include it and in the end that's even uglier than before...


Umm yeah just ignore that its prety much the curiously recuring template pattern that MaulingMonkey mentioned but not as elegent.

Share this post


Link to post
Share on other sites
Ok, I thought I'd post what i have right now. Please tell me if you see anything wrong with it / have suggestions for improvements:


#ifndef CSE_TUPLE_H
#define CSE_TUPLE_H

#include "common.h"
#include "Math.h"

/// Base template for all tuples (vectors, colors, quaternions, etc.)
template<class Self, class T, size_t N>
struct tuple_base
{
/// Data access
inline T& operator() (size_t i) { assert(i<N); return data; }
/// Const data access
const T& operator() (size_t i) const { assert(i>=0&&i<N); return data; }

/// Retrieves pointer to data
T* getDataPtr() { return data; }
/// Retrieves const pointer to data
const T* getDataPtr() const { return data; }

Self& operator+= (const Self& o)
{
for(int i = 0; i < N; ++i)
data += o.data;
return static_cast<Self&>(*this);
}

Self& operator-= (const Self& o)
{
for(int i = 0; i < N; ++i)
data -= o.data;
return static_cast<Self&>(*this);
}

Self& operator*= (const Self& o)
{
for(int i = 0; i < N; ++i)
data *= o.data;
return static_cast<Self&>(*this);
}

Self& operator*= (const T& o)
{
for(int i = 0; i < N; ++i)
data *= o;
return static_cast<Self&>(*this);
}

Self& operator/= (const Self& o)
{
for(int i = 0; i < N; ++i)
data /= o.data;
return static_cast<Self&>(*this);
}

Self& operator/= (const T& o)
{
for(int i = 0; i < N; ++i)
data /= o;
return static_cast<Self&>(*this);
}

template<class Other>
static Self& convert(tuple_base<Other,T,N>& o) { return *reinterpret_cast<Self*>(&o); }

template<class Other, class OT>
static Self convert_cast(const tuple_base<Other,OT,N>& o)
{
Self result;
for(int i = 0; i < N; ++i)
result(i) = static_cast<T>(o(i));
return result;
}

friend Self operator+(const Self& left, const Self& right) { Self result(left); result += right; return result; }
friend Self operator-(const Self& left, const Self& right) { Self result(left); result -= right; return result; }
friend Self operator*(const Self& left, const Self& right) { Self result(left); result *= right; return result; }
friend Self operator/(const Self& left, const Self& right) { Self result(left); result /= right; return result; }

friend Self operator*(const Self& left, const T& right) { Self result(left); result *= right; return result; }
friend Self operator/(const Self& left, const T& right) { Self result(left); result /= right; return result; }
friend Self operator*(const T& left, const Self& right) { Self result(right); result *= left; return result; }

friend bool operator==(const Self& left, const Self& right)
{
bool result = true;
for(int i = 0; i < N; ++i)
result = result && (left.data == right.data);
return result;
}
friend bool operator!=(const Self& left, const Self& right)
{
return !operator==(left,right);
}

protected:
T data[N];
};

/// Templated overload of << operator for printing via cout
template<class Self, class T,int N> std::ostream& operator<< (std::ostream& ostr, const tuple_base<Self,T,N>& tup)
{
ostr << '[';
for(int i = 0; i < N; ++i)
{
ostr << tup(i);
if(i != N-1)
ostr << ", ";
}
return ostr << ']';
}

/// General tuple
template<class T, size_t N>
struct tuple : public tuple_base<tuple<T,N>,T,N> {};

/// General vector
template<class T, size_t N>
struct vector : public tuple_base<vector<T,N>,T,N>
{
vector<T,N>& normalize()
{
T l = 0;
for(int i = 0; i < N; ++i)
l += (*this)(i) * (*this)(i);

assert(l != 0);
if(l == 1)
return *this;

l = 1 / cse::Math<T>::sqrt(l);

for(int i = 0; i < N; ++i)
(*this)(i) *= l;

return *this;
}
};

/// 4-Dimensional vector
struct vec4f : public vector<float,4>
{
vec4f(const float& x, const float& y, const float& z, const float& w)
{
data[0] = x;
data[1] = y;
data[2] = z;
data[3] = w;
}

float& x() { return data[0]; }
float& y() { return data[1]; }
float& z() { return data[2]; }
float& w() { return data[3]; }

const float& x() const { return data[0]; }
const float& y() const { return data[1]; }
const float& z() const { return data[2]; }
const float& w() const { return data[3]; }
};

#endif



The last two structs are there to show how the tuple class is to be used later (having different things such as vector, color etc. with specific functions etc). The casting between data types isn't quite as nice as before, but it works .

Share this post


Link to post
Share on other sites
Quote:
Original post by l0calh05t
But will this work?

*** Source Snippet Removed ***


No, a class cannot be it's own base class.

For "simple" tuples, a few options:

1) Extra class:

template < typename T , size_t N > struct arith_array : arith_array_base< arith_array<T,N> , T , N > {};

2) boost::tuple

3) Nil placeholder with partial specialization (current naming scheme is horrible in this case):

struct nil {};

template < typename T , size_t > struct arith_array_base< nil , T , N > {
...reimplementation of the functions...
};

4) Variant on 3) Have arith_array_base< nil , T , N > inherit from arith_array_base< arith_array_base< nil , T , N > , T , N >. This is not technically self-inheritence, since the two types have (ironically) distinct Self parameter values (nil in one case, arith_array_base< nil , T , N > in the other).

All will have extra work involved if you want simple tuple -> other tuple conversion.

Quote:
Quote:
1) I always place the Self parameter first, to help make it clear when CRTP is being used.
2) "> 0" invariant mantained with signedness (allowing us to generate compile time warnings) rather than run time checks.


You mean for the operator()?


Renamed to operator[] in my example, but yes.

Quote:
Quote:
6) It's easier to implement + in terms of +=, so I do so.
7) All functions listed inline to the class definition are implicitly of inline linkage, so I didn't bother explicitly repeating that.


I don't trust my compiler. (and the functions will probably end up in a separate inline header in the end anyways, something i picked up from 3DGEA)


If you don't trust it to:
1) Inline -- fun fact: the compiler can and will ignore the inline keyword, as it is a hint, not a requisite, and is often completely ignored on account of compiler implementors thinking they're smarter than you (as they all too often are).
2) Use inline linkage (that is, no link errors), I'm at a loss. I think even VS6 gets that right.

Quote:
Quote:
10) I could've safely used arith_array_base as the argument types as well, since I don't use any members specific to Self in the base class, since each class that uses arith_array_base gets their own version (thanks to the Self parameter being unique to the class), no worries about a situation like this:

struct vertex2d : arith_array_base< vertex2d , float , 2 > {};
struct texcoord : arith_array_base< texcoord , float , 2 > {};

int main() {
vertex2d v;
texcoord t;
v = t;
}


error C2679: binary '=' : no operator found which takes a right-hand operand of type 'texcoord' (or there is no acceptable conversion)


Sorry, didn't quite understand this last part here (Mainly the first sentence) Are you just pointing out that each arith_array_base will be a different type (due to the extra template parameter)?


I'm pointing out everywhere "Self" was used in my example (except as a return type), I could've used "arith_array_base" without any problems, since:

1) "each arith_array_base will be a different type", yes
2) None of this code depends on things defined only in the superclass (RE: my first sentance)

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
No, a class cannot be it's own base class.

For "simple" tuples, a few options:

1) Extra class:

template < typename T , size_t N > struct arith_array : arith_array_base< arith_array<T,N> , T , N > {};


Thats what i did (last post):


/// General tuple
template<class T, size_t N>
struct tuple : public tuple_base<tuple<T,N>,T,N> {};



Quote:
2) boost::tuple


That would be suboptimal as it would then not fit in with my other tuples (and if one does need tuples of heterogenous types, there still is the option to use boost::tuple). It is a bit of a naming problem. I would call mine arrays, but I don't think the name really fits, because they aren't really just arrays, but tuples (ordere, finite length lists) of number values. Then again tuple doesn't work that much better either. Oh well. Heh, maybe I should call it a valarray.

Quote:
3) Nil placeholder with partial specialization (current naming scheme is horrible in this case):

struct nil {};

template < typename T , size_t > struct arith_array_base< nil , T , N > {
...reimplementation of the functions...
};


Thats one of the things I don't like about the C++ template syntax. When I specialize a template, I have to reimplement *all* functions, even if I just want to change a few.

Quote:
4) Variant on 3) Have arith_array_base< nil , T , N > inherit from arith_array_base< arith_array_base< nil , T , N > , T , N >. This is not technically self-inheritence, since the two types have (ironically) distinct Self parameter values (nil in one case, arith_array_base< nil , T , N > in the other).

All will have extra work involved if you want simple tuple -> other tuple conversion.


The conversion problem is also more or less solved in my last post

Renamed to operator[] in my example, but yes.

Quote:
If you don't trust it to:
1) Inline -- fun fact: the compiler can and will ignore the inline keyword, as it is a hint, not a requisite, and is often completely ignored on account of compiler implementors thinking they're smarter than you (as they all too often are).
2) Use inline linkage (that is, no link errors), I'm at a loss. I think even VS6 gets that right.


Sorry about that forgot a :-P I was just kidding about the "not trusting" part, at least with what concerns inlining. (loop unrolling is another matter... I once wrote a matrix multiplication with 3 nested loops with 4 iterations each, the compiler didn't unroll them - in release mode - which resulted in a *very* slow method with heaps of jump mispredictions)

If I really want to force the inlining VS does have __forceinline (nonstandard => bad)

Quote:
I'm pointing out everywhere "Self" was used in my example (except as a return type), I could've used "arith_array_base" without any problems, since:

1) "each arith_array_base will be a different type", yes
2) None of this code depends on things defined only in the superclass (RE: my first sentance)


except for the problems it would cause with the copy assign operator, right?

Share this post


Link to post
Share on other sites
Hey, why not just use boost.array as your tuple base since it does everything tuple_base does plus a few things (iterator support, size(), empty(), etc)

template<class T, std::size_t N>
class tuple : public boost::array<T,N>
{
...
}


Or just use it directly.

typedef boost::array<float,3> vec3;
typedef boost::array<int,3> color;


Share this post


Link to post
Share on other sites
Quote:
Original post by Amnesty2
Hey, why not just use boost.array as your tuple base since it does everything tuple_base does plus a few things (iterator support, size(), empty(), etc)

template<class T, std::size_t N>
class tuple : public boost::array<T,N>
{
...
}


That won't work for the exact reason why I needed the macro.

Quote:
Or just use it directly.

typedef boost::array<float,3> vec3;
typedef boost::array<int,3> color;


And what if I want to normalize a vector? Or if I want to multiply it with a matrix? Sure I could have non-member functions for those, but that's kinda circumventing the whole object-oriented design thing.

Share this post


Link to post
Share on other sites
Quote:
Original post by l0calh05t
And what if I want to normalize a vector? Or if I want to multiply it with a matrix? Sure I could have non-member functions for those, but that's kinda circumventing the whole object-oriented design thing.


Not necessarily. One of the concepts central to OOP, and the one which I think you probably have in mind, is encapsulation. Objects typically provide methods for this reason, not simply because it's cool to write Object.foo() rather than foo(Object).

Since your tuple classes already provide methods for accessing and modifying the internal data, there is no conceptual OOP reason for the normalization or multiplication methods to be member functions (for although your classes encapsulate the representation of the data, the data itself is not private to the class). Indeed, making these operations non-member functions may improve encapsulation. Suppose, for example, that you change the internal representation of the data in your tuple. Then potentially you will have to modify every member (and non-member friend) function which accesses the data. If your vector operations are (non-friend) non-member functions, then this will not be an issue.

Of course, there may be other reasons for allowing the functions to access the private variables of the class (efficiency, for example). But from a design perspective, OOP is not simply a case of making everything a member function.

Share this post


Link to post
Share on other sites
Quote:
Original post by MumbleFuzz
Not necessarily. One of the concepts central to OOP, and the one which I think you probably have in mind, is encapsulation. Objects typically provide methods for this reason, not simply because it's cool to write Object.foo() rather than foo(Object).

Since your tuple classes already provide methods for accessing and modifying the internal data, there is no conceptual OOP reason for the normalization or multiplication methods to be member functions (for although your classes encapsulate the representation of the data, the data itself is not private to the class). Indeed, making these operations non-member functions may improve encapsulation. Suppose, for example, that you change the internal representation of the data in your tuple. Then potentially you will have to modify every member (and non-member friend) function which accesses the data. If your vector operations are (non-friend) non-member functions, then this will not be an issue.


It doesn't have to be an issue, even if they are member functions, that's what these can be used for, as they can be used both by member and non-member functions (i used the function operator not the subscript operator mainly for consistency with my matrix class, which obviously can't trivially use the subscript operator):


template<class Self, class T, size_t N>
struct tuple_base
{
/// Data access
T& operator() (size_t i) { assert(i<N); return data; }
/// Const data access
const T& operator() (size_t i) const { assert(i>=0&&i<N); return data; }
/*...*/
};



But it is also worth to be mentioned that in this case, changing the base representation could also be pretty much catastrophic (ie lots of rewriting) in many other places, esp. interfaces to the graphics library, as sending the data to opengl requires the representation to be known, so I can't maintain perfect encapsulation either way. And I don't really consider making a correctly ordered copy of all vector data before sending it an option. And yes, I do know the internal representation of a struct/class is not 100% defined. Hell, it can't even be guaranteed a byte contains 8 bits on all platforms.

Quote:
Of course, there may be other reasons for allowing the functions to access the private variables of the class (efficiency, for example). But from a design perspective, OOP is not simply a case of making everything a member function.


I agree, but I don't see any reason *not* to make them member functions if it makes sense and there is no damage in doing so.

Oh and one more reason I don't like the typedef suggestion:
Typedefs are not strongly typed, so I can just assign a color to a vertex etc. In my version one has to explicitly state that one wants to perform such a (potentially incorrect) conversion, which I personally think is better. (I guess BOOST_STRONG_TYPEDEF *would* solve that though)

Share this post


Link to post
Share on other sites
Quote:
Original post by l0calh05t
It doesn't have to be an issue, even if they are member functions, that's what these can be used for, as they can be used both by member and non-member functions
To some degree, perhaps. But by making the vector operations non-members you can guarantee that you won't accidently reference a private variable. Relying on the compiler is generally preferable to relying on your own memory, especially since anyone else using or maintaining your code only has the luxury of the former.

Quote:
But it is also worth to be mentioned that in this case, changing the base representation could also be pretty much catastrophic (ie lots of rewriting) in many other places, esp. interfaces to the graphics library, as sending the data to opengl requires the representation to be known, so I can't maintain perfect encapsulation either way. And I don't really consider making a correctly ordered copy of all vector data before sending it an option. And yes, I do know the internal representation of a struct/class is not 100% defined. Hell, it can't even be guaranteed a byte contains 8 bits on all platforms.
This is certainly an important consideration in this case, but it is particular to the API, and not a general argument related to OO design.

Quote:
I don't see any reason *not* to make them member functions if it makes sense and there is no damage in doing so.
There may not be any immediate damage, but you can't guarantee this for the long term (particularly if other programmers work on your code) unless you invoke the guarantees of the standard by making the functions non-members.

Admittedly, the amount of code you've posted is trivial, and you're unlikely to encounter any major maintenance headaches as it is. But you argued that it was good objected-oriented design, which, for the reasons outlined in the article, I (and many others) would argue it most certainly is not.

Share this post


Link to post
Share on other sites
Quote:
Original post by MumbleFuzz
To some degree, perhaps. But by making the vector operations non-members you can guarantee that you won't accidently reference a private variable. Relying on the compiler is generally preferable to relying on your own memory, especially since anyone else using or maintaining your code only has the luxury of the former.


True.

Quote:
This is certainly an important consideration in this case, but it is particular to the API, and not a general argument related to OO design.


No, but relevant to this particular situation.

Quote:
There may not be any immediate damage, but you can't guarantee this for the long term (particularly if other programmers work on your code) unless you invoke the guarantees of the standard by making the functions non-members.


Due to the circumstances, if someone changes the internal representation it would break other parts of the code, which are not connected to the "memberness" of this class' functions, therefore I would say there can also be no damage in doing as I did in the future either *in these particular circumstances*

Quote:
Admittedly, the amount of code you've posted is trivial, and you're unlikely to encounter any major maintenance headaches as it is. But you argued that it was good objected-oriented design, which, for the reasons outlined in the article, I (and many others) would argue it most certainly is not.


Actually, I didn't argue it was good design, only that it is better design than using a typedef (mainly for reasons of type safety, although i did not mention that in the original post). And in this particular situation I believe it is really just a matter of preference how the functions are implemented.

And I still have to get used again to actually having non-member functions at my disposal. (Didn't have much free time to write my own programs, and at work&university I've been forced to write Java, or some nigh-useless, obscure language such as scheme [oh the horror...] )

Share this post


Link to post
Share on other sites
Quote:
Original post by l0calh05t
Actually, I didn't argue it was good design, only that it is better design than using a typedef
Oh, ok. Sorry, I wasn't certain of what you were implying.

Quote:
And I still have to get used again to actually having non-member functions at my disposal.
Once you're more accustomed to the standard library, I'm sure you'll come to appreciate them [wink].

Share this post


Link to post
Share on other sites
Quote:
Original post by MumbleFuzz
Oh, ok. Sorry, I wasn't certain of what you were implying.


No need to apologize, no harm done. And I like a good discussion ;-)

Quote:
Once you're more accustomed to the standard library, I'm sure you'll come to appreciate them [wink].


Or rather as soon as I am more accustomed with it *again*, after 2, almost 3, years of not writing anything in C++ you do forget a few things.

(And about the "oh the horror" after scheme for any scheme proponents that should hang around here - if any: In the first semester we were forced to implement "objects" in scheme and i remember many a last line of code which looked approximately like this: ))))))))))))))))))

Share this post


Link to post
Share on other sites
Quote:

That won't work for the exact reason why I needed the macro.


What?
You won't need the macro at all. The compiler will give you the default copy/assign which are are quite enough. In fact implementing them at all (using the macro in this case) is really bad idea i suspect*

class vecy : public tuple<int,2>
{
public
// Don't need this
// CSE_TUPLE_ASSIGN_MACRO(vecy,int,2)
// dont need this either
// vecy() {}
};

*
Your class would not be a aggregate if you implemnt any of the ctors and that would prevent you from doing nice array-like initializations like so

tuple<int,4> quad = { 2, 3, 4, 5 }; // error

I for one would really like that syntax for these small array-types.
Remove these lines from your tuple class and that array init will work.
tuple() {} and protected:

tuple<int,4> quad = { 2, 3, 4, 5 }; // fine

Yah, inheriting from boost.array would make it a non-aggregate aswell however it already was so i wansnt doing any more damage :)
you can however make boost.array a member var and just delegate to it. However that doesnt save you much work at all.
But for the love of god add standard iterator support to your tuple class.

Share this post


Link to post
Share on other sites
Quote:
Original post by Amnesty2
What?
You won't need the macro at all. The compiler will give you the default copy/assign which are are quite enough. In fact implementing them at all (using the macro in this case) is really bad idea i suspect*

class vecy : public tuple<int,2>
{
public
// Don't need this
// CSE_TUPLE_ASSIGN_MACRO(vecy,int,2)
// dont need this either
// vecy() {}
};


That will not work. You are forgetting about the operators.

Quote:
*
Your class would not be a aggregate if you implemnt any of the ctors and that would prevent you from doing nice array-like initializations like so

tuple<int,4> quad = { 2, 3, 4, 5 }; // error

I for one would really like that syntax for these small array-types.
Remove these lines from your tuple class and that array init will work.
tuple() {} and protected:

tuple<int,4> quad = { 2, 3, 4, 5 }; // fine


That overloading the "braces constructor" isn't possible in C++ (yet?) is not my fault.

Quote:
Yah, inheriting from boost.array would make it a non-aggregate aswell however it already was so i wansnt doing any more damage :)
you can however make boost.array a member var and just delegate to it. However that doesnt save you much work at all.
But for the love of god add standard iterator support to your tuple class.


Honestly... why? Sorry, but pretty much all stl algorithms make no sense on this tuple (except perhaps for-each, but implementing the iterators just for that seems like a waste of time)

Share this post


Link to post
Share on other sites
Quote:

That will not work. You are forgetting about the operators.

What? Can you explain because you might need to redefine some operators in your dervied classes but you DON'T need to define copy/assignment operators for your tuple class. And unless you add some complex data types in your dervied classes you wont need copy/assignment operators their either.
Quote:

That overloading the "braces constructor" isn't possible in C++ (yet?) is not my fault.

You don't need 'overload' it.
Simply remove
tuple() {}
and the protected keyword in your tuple class and it will work.

tuple<char,3> abc = { 'a', 'b', 'c' } // this is now fine, and works as expected

It is up to you really if you want to make your tuple class an aggregate-type.
Personally, initializing like this is very worthwhile in this case. IMO
Quote:

Honestly... why? Sorry, but pretty much all stl algorithms make no sense on this tuple (except perhaps for-each, but implementing the iterators just for that seems like a waste of time)

'on this tuple'
Of course they make sence. Its a generic finite array. Yes, they don't make much sence on specific instances like color/vec4 which you intend to use it for right out of the box. However, once this class is all polished up nice and neat you will find it useful in lots of other places where the context would make a lot of sence for the algorithms.

Just trying to help you out here.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement