any way to do this without the ugly macro?

Started by
26 comments, last by l0calh05t 17 years ago
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]
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.
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.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
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)
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.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
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).
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...
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)?
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?
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.
"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

This topic is closed to new replies.

Advertisement