Impossible challenge

Started by
15 comments, last by cache_hit 14 years, 7 months ago
The below post of Washu's may have part of the answer to this one (Posted - 7/16/2009 6:28:49 PM):

C++ operator[]


Aint it wicked!

[Edited by - iMalc on September 12, 2009 6:20:32 PM]
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Advertisement
I don't think that answers the OP's question. It doesn't give you a matrix templatized on the *size* of the matrix, or templatized on anything at all for that matter.
Okay so that only shows how to access individual members as an array, rather than trying to access an array as individual members.

I don't believe there is a way besides explicit specialisation, or code generation.
How would one expect to have the compiler pick the names of the members and how many of them there are according to integer template parameters? I.e. what's to stop me from doing this:
matrix<1,2,float> m;m.z = 42;
Whilst allowing this:
matrix<1,3,float> m;m.z = 42;


A goal of avoiding explicit specialisation entirely, is nuts. There are certain operations such as cross-product that only make sense with a certain number of dimensions.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by iMalc
Okay so that only shows how to access individual members as an array, rather than trying to access an array as individual members.

I don't believe there is a way besides explicit specialization, or code generation.
How would one expect to have the compiler pick the names of the members and how many of them there are according to integer template parameters? I.e. what's to stop me from doing this:
matrix<1,2,float> m;m.z = 42;
Whilst allowing this:
matrix<1,3,float> m;m.z = 42;


A goal of avoiding explicit specialisation entirely, is nuts. There are certain operations such as cross-product that only make sense with a certain number of dimensions.



True, but you don't need explicit specialization for that.

template<int Rows, int Columns, class T>class Matrix{public:   template<int OtherRows, int OtherColumns, class T> //Input matrix can have different dimensions   boost::enable_if<boost::equal_c<Columns, OtherRows>, void>::type //but only if Columns == Rows.   The result of this expression is void, and thus this function returns void   Multiply(const Matrix<OtherRows, OtherColumns, T>& rhs)   {   }};


writing m.Multiply(other) will only compile if m.Columns == other.Rows. For cross product you can do something similar. Cross product is only defined for 3 or 7-dimensional vectors, so you write it as:

   boost::enable_if<boost::equal_c<Columns*Rows, 3>, void>::type   CrossProduct(const Matrix<Rows, Columns, T>& rhs)   {   }

Here the argument must be a matrix of the exact same type. The condition this time is that Columns*Rows == 3. The function doesn't exist otherwise. This condition might seem odd, but since 3 is prime the only possibilities are 1,3 and 3,1. This means it handles row major and column major vectors. Add an or clause for the dimension 7. Or better yet, do the following:

template<int Rows, int Columns>struct supports_cross_product { enum { value = 0; } };template<>struct supports_cross_product<1, 3> { enum { value = 1; } };template<>struct supports_cross_product<1, 7> { enum { value = 1; } };template<>struct supports_cross_product<3, 1> { enum { value = 1; } };template<>struct supports_cross_product<7, 1> { enum { value = 1; } };   boost::enable_if<supports_cross_product<Rows, Columns>, void>::type   CrossProduct(const Matrix<Rows, Columns, T>& rhs)   {   }


The OP said he didn't want to use any template specialization because of code bloat, but this generates 0 code. It all resolves down to nothing.


Having the compiler pick the names is also possible via some template metaprogramming hackery. The method you would use is similar to what boost uses all over the place and they call "placeholders". There's an inherent limitation here, just as there is in boost, in that you can't have an unlimited of such "placeholders". They're all predefined, but in essence you would do something like this:

namespace matrix_vars{   struct w { float w; };   struct x { float x; };   struct y { float y; };   struct z { float z; };   typedef boost::mpl::vector<x, y, z, w> default_vars;}template<int Rows, int Columns, class T, class VarNames = matrix_vars::default_vars>class Matrix    : public inherit_from<get_n_vars<Rows*Columns, VarNames>::type>{public:}


get_n_vars just walks through the VarNames list for a maximum of n items, making a new type list out of each one and stopping after that.

The end result of all this is that you inherit from each of the "placeholders", and so you get a public member with the correct name. If you want to change the way different items are mapped to different variable names, make up a new var_names list.

This might look like a ton of template stuff that's going to result in code bloat, but most of it is only compile-time type magic and doesn't result in extra code bloat.

In practice the "placeholder" classes will need to be a little more complicated, to allow for conversions and casting among other things, but nevertheless I think it's a good start.

[Edited by - cache_hit on September 12, 2009 9:13:40 PM]
cache_hit,

Several bugs with your code first of all...

enum{ value = 1 };


not

enum{ value = 1; }


also, you forgot keyword "typename" on your use of enable_if.

when you write,

boost::enable_if<boost::equal_c<Columns*Rows, 3>, void>::type


I haven't used equal_c, but wouldn't it be better to have written,

boost::enable_if_c<Columns*Rows==3>::type


Quote: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 think we can all agree that "m.field<float, 0>" is a much uglier syntax than "m.x()" which is trivial, so I see no advantage to that suggestion. However, I agree it would make sense to enable_if these accessors only for vectors of the appropriate size.

I'm still having a bit of trouble understanding this example,

template<int Rows, int Columns, class T, class VarNames = matrix_vars::default_vars>class Matrix    : public inherit_from<get_n_vars<Rows*Columns, VarNames>::type>{public:}



I never thought of using inheritance in this way...may this result in non-optimal alignment of the struct though?

Also, this is a bit off topic, but there are several more problems in this:
a) you should have templated on an unsigned size
b) your return type here is void, but should be either Matrix<Rows,OtherColumns,T> or Matrix<Rows,OtherColumns,OtherT>
c) why not use operator*?
d) No need to resort to enable_if here, as it can be done more cleanly and simply without it
e) Even if using enable_if, there's no need to use equal_c because you could have just used enable_if_c

template<int OtherRows, int OtherColumns, class T> //Input matrix can have different dimensions   boost::enable_if<boost::equal_c<Columns, OtherRows>, void>::type //but only if Columns == Rows.   The result of this expression is void, and thus this function returns void   Multiply(const Matrix<OtherRows, OtherColumns, T>& rhs)   {   }


I would do something like this instead:

template<unsigned P>matrix<M,P,Real> operator*(const matrix<N,P,Real> &m) const;
I basically just typed all that off the top of my head, so I'm not surprised at all that there's a lot of bugs :) I was mostly just trying to illustrate the idea, and leave the implementation details out.

I've only briefly looked over your comments, but they all seem valid. FWIW I had the Multiply functions return a void because that way there's no copy made. But it's just an implementation detail that can be changed to suit the needs of your library.

Quote:
I'm still having a bit of trouble understanding this example,


template<int Rows, int Columns, class T, class VarNames = matrix_vars::default_vars>

class Matrix

: public inherit_from<get_n_vars<Rows*Columns, VarNames>::type>

{

public:

}



It's easiest to understand if you consider how boost::inherit works. It takes every item passed to it, and just inherits from it. so

class foo : public inherit<a, b, c, d>
{
};

is equivalent to writing

class foo : public a, public b, public c, public d
{
};

because of this, you need to be careful about making sure the bases are POD types that don't inherit from anything else, or else you might run into the dreaded diamond. But the key point is that if a, b, c, and d contain public members, then foo contains those same public members. And if those members are classes that are convertible to float and allow assignment, then you actually do have foo.x, foo.y, foo.z syntax.

The point of the get_n_vars thing is that you might have some predefined compile-time limit of how many "embedded variables" like x, y, z, and w can be supported. In my example I specified a default mapping with the matrix::default_vars typelist, which contains placeholders x, y, z, w. This means matrix.x refers to matrix[0][0], matrix.y refers to matrix[0][1], etc. But matrices are different sizes and you might want different variable names. Specifying a different list such as <w, y, x, z> changes what member name accesses which variable. If you specify a 2x2 matrix but the list only has 3 items in it, then matrix[1][1] would be inaccessible through one of these embedded members. The idea is just that you specify a list of types, each of which knows how to access an item in the array. You pull out the first n of these items, where n = Rows*Columns and inherit from them. This way you get an embedded member variable for each item in the matrix.

This topic is closed to new replies.

Advertisement