Jump to content
  • Advertisement
Sign in to follow this  
yahastu

Impossible challenge

This topic is 3203 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

I'm fairly sure there is no good solution to this problem, but there's no harm in posing it. Goal: a template sized matrix/vector class that is POD and has ".x, .y, .z" style access, and doesn't cause code bloat. One way to achieve this is by using an unnamed struct, which works in MSVC, but unfortunately doesn't work in other compilers:
template<unsigned M, unsigned N, typename Real>
struct matrix
{
    union{
        Real data[M*N];
	struct {Real x, y, z, w;};
    };

    //lots of member funcs
};


Another alternative is to use explicit template specialization. But the problem with that is twofold: 1) Code bloat because every member function must be rewritten. This could be somewhat minimized by using wrappers but still is quite ugly and makes code less maintainable. 2) Requires constructor to initialize data ptr, making it non-POD.
//generic version
template<unsigned M, unsigned N, typename Real>
struct matrix
{
    Real data[M*N];

    //lots of member funcs
};

template<typename Real>
struct matrix<2,1>
{
    const unsigned M = 2, N = 1;
    Real x,y;
    Real *data;
    
    matrix() : data(&x) {}

    //lots of member funcs
};

template<typename Real>
struct matrix<3,1>
{
    const unsigned M = 3, N = 1;
    Real x,y,z;
    Real *data;
    
    matrix() : data(&x) {}

    //lots of member funcs
};

template<typename Real>
struct matrix<4,1>
{
    const unsigned M = 4, N = 1;
    Real x,y,z,w;
    Real *data;
    
    matrix() : data(&x) {}

    //lots of member funcs
};



Now, we can get something very close to perfect by using member functions,
template<unsigned M, unsigned N, typename Real>
struct matrix
{
    Real data[M*N];

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

    //lots of member funcs
};



Unfortunately, calling code still has to use the unsightly ".x()" form. We could eliminate that necessity by using macros,
#define x x()
#define y y()
#define z z()

matrix<3,1> v;
v.x = 1;

#undef x
#undef y
#undef z



However, now we have to wrap these ugly macros around the calling code and abstain from using any variables called x y or z. Another alternative is to use __declspec(property), but this is also MSVC specific,
template<unsigned M, unsigned N, typename Real>
struct matrix
{
    Real data[M*N];

    Real get_x() const{ return data[0]; }
    Real get_y() const{ return data[1]; }
    Real get_z() const{ return data[2]; }
    void set_x(Real x){ data[0] = x; }
    void set_y(Real y){ data[1] = y; }
    void set_z(Real z){ data[2] = z; }

    __declspec(property(get = get_x, put = set_x)) Real x;
    __declspec(property(get = get_y, put = set_y)) Real y;
    __declspec(property(get = get_z, put = set_z)) Real z;


    //lots of member funcs
};



So in conclusion, I see only 2 portable ways, one relies on a horribly ugly macro hack, the other causes intolerable code bloat and loses POD. Then there are ways that achieve desired effect, but are Microsoft specific. Arggh.....any more ideas? [Edited by - yahastu on September 11, 2009 3:25:01 PM]

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by yahastu

Goal: a template sized matrix/vector class that is POD and has ".x, .y, .z" style access, and doesn't cause code bloat.


x,y,z access is only relevant for small vectors.

How would you express Matrix<200,200>?

And code bloat is somewhat less of a problem these days. If you have vector3 and vector4, then that's two classes.

Quote:
union{
Real data[M*N];
struct {Real x, y, z, w;};
};


So (M,N) can be (4,1), (2,2) and (1,4). I can't think of any practical reason where this would be applicable, since only other semantics differ. (4,1) and (1,4) are usually used in different context than (2,2), so x,y,z,w is misleading.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
There's also the static pointer to member array trick.


Yes, but that requires a constructor so breaks the POD requirement.

Quote:
x,y,z access is only relevant for small vectors.

How would you express Matrix<200,200>?


The reason I want x/y/z access is for syntax. This class is only for use with small sized matrices and vectors where performance is paramount. For larger matrices, it does not make sense to use stack allocation.

Quote:
And code bloat is somewhat less of a problem these days. If you have vector3 and vector4, then that's two classes.


Code bloat is problematic primarily because it makes the code less maintainable. It is also substantial because there would need to be 4 versions, and each one has about 30-40 member functions. I think that this might cause some small loss of performance, and since this class is designed specifically to be high performance, it is not acceptable to sacrifice even a small amount of performance for a stylistic improvement in my opinion.

Quote:

So (M,N) can be (4,1), (2,2) and (1,4). I can't think of any practical reason where this would be applicable, since only other semantics differ. (4,1) and (1,4) are usually used in different context than (2,2), so x,y,z,w is misleading.


It is not really a problem to have x/y/z/w available on matrices if the programmer knows they are only intended to be used on Nx1 vectors. Out of coincidence, the implementation may also allow it to work on 1xN vectors but that is not a requirement I care about because my library is consistent in using column vectors.

Share this post


Link to post
Share on other sites
Quote:
Original post by yahastu]

The reason I want x/y/z access is for syntax. This class is only for use with small sized matrices and vectors where performance is paramount. For larger matrices, it does not make sense to use stack allocation.


Ok... What are the limits then? What are the naming rules?

Can you provide realistic use cases on limits and naming convention?

Perhaps m.x.y? Or m[0].x? Or m.xw? I'm not entirely clear how this would add much semantic value for non-trivial types (2-, 3- and 4-D vectors).

Quote:
Code bloat is problematic primarily because it makes the code less maintainable. It is also substantial because there would need to be 4 versions, and each one has about 30-40 member functions. I think that this might cause some small loss of performance, and since this class is designed specifically to be high performance, it is not acceptable to sacrifice even a small amount of performance for a stylistic improvement in my opinion.


The code bloat related to templates usually refers to generated code, where there are many instantiations, perhaps some non-obvious ones, which cause the executable to bloat.

Share this post


Link to post
Share on other sites
Quote:
Original post by yahastu
Yes, but that requires a constructor so breaks the POD requirement.

No, it doesn't.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by yahastu
Yes, but that requires a constructor so breaks the POD requirement.

No, it doesn't.


I assumed you were referring to [6/27/2005 8:00:21 AM] (you didn't specify). I take it then you are referring to [6/27/2005 9:04:27 AM ?

The problem with that example is that it assumes a 3-dimensional vector. I can generalize it (as shown below), but this relies on a non-standard zero-sized array extension in MSVC.

Moreover, it also does not work for M < 4, which would require vec to be specialized for those 4 dimensions...in other words, it just takes the problem back to square one.


template<unsigned M>
struct vec
{
float x,y,z,w;
float data[ ((int)M-3) > 0 ? (M-3) : 0 ];

float &operator[](unsigned i)
{
return ((float*)this);
}

const float &operator[](unsigned i) const
{
return ((float*)this);
}
};




Quote:
Ok... What are the limits then? What are the naming rules?

Can you provide realistic use cases on limits and naming convention?

Perhaps m.x.y? Or m[0].x? Or m.xw? I'm not entirely clear how this would add much semantic value for non-trivial types (2-, 3- and 4-D vectors).


My requirements are:
1) Fully general; allows for any static size from 1,1 up to N,N
1) POD
2) No overhead
3) No specializations of the base class that require duplicating member functions

My desires is:
4) Able to access as ".x" or ".y" or ".z"

As it is easy to add ".x()" access support with all my requirements, the only syntactic improvement (imo) is exactly ".x", no variations.

The syntactic value I place upon this syntax is just because I find it to be more readable and "nice".

I have recently heard that newer versions of g++ do reputedly support anonymous structs, so that makes that option a bit more attractive. Is this widely supported as an extension by other compilers?

Share this post


Link to post
Share on other sites
Quote:
Original post by yahastu
I assumed you were referring to [6/27/2005 8:00:21 AM] (you didn't specify).

I thought that specifying "static pointer to member array trick" would make it obvious I was referring to the post involving a static pointer to member array.

Share this post


Link to post
Share on other sites
Honestly, if this is important to you (and you won't back down on any of your requirements), then you should just bite the bullet and move to code-generation. Biggest problem would be that you'd need to somehow know up front which specializations are required. Of course, I can't see wanting named member access for anything past four dimensions, and there are lots of good reasons to have your float3 and float4 be VERY different objects (SIMD alignment requirements...). So yeah, I don't have a great idea (I'm guessing you don't want to go the code-gen route), but I also think you're painting yourself into a corner with a pile of unrelated and at-odds requirements.

Share this post


Link to post
Share on other sites
Modern C++ Design discusses how to implement templatized mixins via inheritance. He creates a generic inherit_from template that takes a typelist and inherits from each item in the list. You can use a similar technique here. Note that boost provides boost::inherit which is a variadic template that achieves a similar effect.

Using this technique, what you would do is:

template<typename T, int Size>
class Matrix : public inherit_from<Repeat<T, Size> >
{
};

Repeat is something you'd define yourself that creates a boost::mpl::list of the appropriate size. Shouldn't be hard. This would make a class that inherits from T Size times. Of course you can't inherit from the same thing more than once, and you can't inherit from primitive types, so boost::inherit wouldn't work here. It would work to use the implementation discussed in Modern C++ Design however. Using that implementation, you would not be able to write m.x, m.y, m.z, m.w, but you would be able to write m.field<float, 0> or something similar, i don't remember 100%.

Share this post


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

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!