|
Boost : |
From: Jason Kraftcheck (kraftche_at_[hidden])
Date: 2006-11-10 12:51:51
I think that the two important issues where are:
1) Boost should have these types defined outside of any domain-specific library.
I think they equivalent to other types such a complex numbers, and should exist
as primitives for use in many different libraries. There seems to be some
support for this from others:
http://lists.boost.org/MailArchives/ublas/2006/11/1519.php
2) Should a vector be defined as a column matrix or a separate template.
This is the principal differentiator between your implementation and mine. The
other issues are just simple features that can be added to one or the other.
My argument for a column matrix definition of a vector is that it is closer to
the mathematical concept we are trying to model. All matrix operations
(multiplication between vectors and matrices, transpose, etc) work for vectors
as well as matrices w/out needing special vector forms of the operations.
My understanding of your argument for vectors as a separate class is that it
allows the matrix class to be defined as an array of vectors, where each vector
is a row of the matrix. This, in turn, allows for simpler coding of matrix row
operations for algorithms such as Gauss-Jordan elimination.
My implementation also provides row access to matrices as 1xC matrices, and
provides vector specific operations (vector product, dot product) for both Rx1
and 1xC matrices. The only drawback to my approach is that a reinterpret_cast
is required to return a reference to a row as a 1xC matrix. It is portable to
do so, as the underlying cast is just operating on array data (no alignment
issues for individual members, no vtable, etc.) But it is ugly.
Overall, 1) what I'd most like to see. I'm willing to contribute to whichever
implementation ends up being used for 1).
- jason
Jason Hise wrote:
> On 11/9/06, Jason Kraftcheck <kraftche_at_[hidden]> wrote:
>> I fail to see how row operations make the implementation cleaner or easier.
>> Most operators can be implemented with a single loop in both cases. For example:
>> private:
>> Type data[R*C];
>> public:
>> matrix<R,C,Type>& operator+=( const matrix<R,C,Type>& other ) {
>> for (unsigned i = 0; i < R*C; ++i)
>> data[i] += other.data[i];
>> return *this;
>> }
>>
>
> I was thinking of the inverse algorithm specifically, and other
> algorithms in general. I'd like to distinguish here between a
> function which is a necessary part of the interface to the type, and
> algorithms which are separate extensions that operate on that type.
> Being able to perform elementary row operations on matricees is
> important (I just had to implement the simplex algorithm for a class I
> am taking). However, it may be more important to provide for
> matricees which do not have fixed sizes at compile time than for those
> that do (simplex required me to be able to insert rows and columns at
> run time).
>
>>> There are two usability features that my implementation provides which
>>> yours does not. The first is being able to index a vector by name
>>> (_x, _y, _z, _w). This is easily implemented using a properly scoped
>>> enum. I used leading underscores for my component names as a
>>> convention to indicate that they were property names, though
>>> admittedly this naming convention could be debated.
>>>
>>> vector <float, 3> up;
>>> up[_x] = 0;
>>> up[_y] = 1;
>>> up[_z] = 0;
>>>
>> I've never found such a convention very useful. It is a trivial thing to add if
>> others do.
>
> I think it falls into the same category as not using magic numbers.
> To give another example, if you use a vector to store a color, being
> able to refer to the components as r, g, b, and a channels instead of
> by number improves readability. But perhaps enumerating these
> constants is better left to client code.
>
>>> Another thing that my code allows that yours does not is explicit
>>> construction of a vector from the number of parameters that matches
>>> the size (at least from 1 through 4), using the SFINAE techniques of
>>> boost.enable_if. It can be much nicer for client code to be able to
>>> write:
>>>
>>> vector <float, 3> up (0, 1, 0);
>>>
>>> than to have to write the code segment I used for my previous example.
>> That is a useful feature. I had considered several ways of implementing it.
>> However, I had doubts about all of them, so I didn't provide any.
>>
>> 1) varargs
>> o matrix( Type ii, ... );
>> o no type checking on arguments
>> 2) multiple constructors with different numbers of args
>> o what you have implemented
>> o seems conceptually wrong to have a four-arg constructor
>> for a 2-element vector, or visa versa.
>> o doesn't work for larger matrices
>
> This is why I used boost::enable_if... it makes it impossible to call
> a 4 arg constructor on a two component vector and vice versa. The
> functions can never be instantiated if the dimensions don't match.
> The code could probably be further improved using BOOST_PP to
> automatically generate an arbitrary number of constructors to match
> larger numbers of initializers.
>
>> 3) one constructor with unrolled assignment
>> o most of the same limitations as 2)
>> o one function
>> o safe (will never write off the end of the array)
>> o matrix( Type a, Type b , Type c = 0, Type d = 0,
>> Type e = 0, Type f = 0, Type g = 0, Type h = 0,
>> Type i = 0, Type j = 0, Type k = 0, Type l = 0,
>> Type m = 0, Type n = 0, Type o = 0, Type p = 0 ) {
>> switch (R*C) {
>> case 16: data[15] = p;
>> case 15: data[14] = o;
>> ...
>> case 1: data[ 0] = a;
>> }
>> o excessively large argument list for e.g. a 2x1 matrix
>>
>> - jason
>
> Another alternative is to use creative operator overloading:
>
> vector<float, 3> up ((vec_builder<float>, 0, 1, 0));
>
> or:
>
> vector<float, 3> up (vec_builder<float>(0)(1)(0));
>
> or maybe:
>
> vector<float, 3> up;
> up << 0 << 1 << 0;
>
> though I do happen to prefer my former solution, which looks and acts
> like a constructor call.
>
> -Jason
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk