tags:

views:

230

answers:

6

Hello.

I am implementing an n-dimensional array class which is a template as follows (Note that the data is stored in a linear array whose length is the product of all the dimensions):

template< class valType, int rank >
class NDimensionalArray
{
public:

private:
    valType* m_data;
    int* m_dimensions;
    int m_rank;
};

So the idea is that a user (me) can specify an array of rank 2, and of a certain dimension, ie:

NDimensionalArray<double,2> matrix(10,10);

Now the difficulty is in specializing constructors for 1->n dimensions, each constructor takes n parameters where n is the rank of the array. Now I thought of using a valarray like is used in printf(), however with this defining a 1-dimensional array with 2 dimensions ie:

NDimensionalArray<double,1> matrix(10,10);

would be perfectly acceptable behavior. Is there some neat trick I can use to let the compiler do the repetition? Realistically so long as I know the rank, and have the length of each dimension the constructor can be generic:

{
    int nElements = m_dimensions[0];
    for ( int i=1 ; i<m_rank ; ++i )
        nElements *= m_dimensions[i];
    m_data = new valType[nElements];
}

Edit: Note that a similar operation will be needed for accessors.

Also I have considered the option of a constructor which looks like:

NDimensionalArray( const NDimensionalArray<int,1>& dimensions );

Which could be used like:

NDimensionalArray<int,1> dimVec(2); // Need a specification for 1-dimensional arrays.
dimVec(0) = 10;
dimVec(1) = 10;
NDimensionalArray<double,2> matrix(dimVec);

This would be a viable solution, but its ugly compared to the use I would like. Also accessing multi-dimensional arrays would become a serious pain, and seriously slow having to construct a dimension vector for each access.

+2  A: 

I think the best solution is to take a vector of ints and let the constructor validate it against the template parameter 'rank'.

NDimensionalArray matrix(std::vector<int> matrixDimensions) 
{
    if (matrixDimensions.size() != rank) 
    {
        throw SomeException();
    }

    ...
}

I don't think any compiler trick can be an alternative here. (Except perhps using macros, if you can think of something, although that wouldn't be a compiler trick strictly speaking.)

Frederick
Ya I was worried about this, see my edit above. I'm really hoping to find a nice clean solution which would allow the 'natural' use of the class. It really becomes an issue when we think about accessors. I mean the vector in the constructor is one thing, but accessing an array with 1 million elements would be insanely slow if we had to use a vector each time.
DeusAduro
@DeusAduro it's not slow if you specify the size beforehand and access the elements to be processor cache friendly. I can't see any way to make it different, since you are going to need an array allocated at the heap, and not at the stack
Edison Gustavo Muenz
You're right. In that case, use the variable argument thing. Both in the constructor and the accessor/mutator and validate that. The problem here is that you cannot 'templatize' the number of arguments of your functions.
Frederick
@Edison I think it will be slow and it has nothing to do with being cache friendly in this case, its the fact that I have to construct a vector, or some other indexer for each access, these objects are inherently temporary, I do an access and then discard it. Now I could implement more compicated schemes where the indexers can be iterated along specific dimensions of the array... I am looking for a simple interface with moderate efficiency.
DeusAduro
+1  A: 

Not a direct answer, but check out the blitz library.

keraba
+1  A: 

You could take a std::tr1::array. Hmm:

#include <array>

template< class valType, int rank >
class NDimensionalArray
{
public:
   NDimensionalArray(const std::tr1::array<rank>& dims);
   // ...
};

NDimensionalArray<double,2> foo({10,10});

NDimensionalArray<double,2> bar({10}); // second dimension would be 0

NDimensionalArray<double,1> baz({10,10}); // compile error?

I'm not actually sure if that works! I'll run it through comeau.

Edited As per the comments, looks like this approach would look more like:

std::tr1::array<2> dims = {10, 10};
NDimensionalArray<double,2> foo(dims);
Todd Gardner
It won't work, because aggregate initializers are only permissible when initializing variables, not in arbitrary expressions (such as function and constructor arguments).
Pavel Minaev
Couldn't get comeau to find the tr1 array header, but now that you say that, I think I remember it biting me in the past.
Todd Gardner
Don't quote me on this, but I believe this will be valid C++0x code as initializer lists change into a full blown type (std::initializer_list<int> in this case): http://en.wikipedia.org/wiki/C%2B%2B0x#Initializer_lists
David Rodríguez - dribeas
Also: NDimensionalArray<double,2>foo( (std::tr1::array<2>){10,10} ); could do the trick (it does with g++ >= 4.0)
David Rodríguez - dribeas
A: 

There's no good way to do it in C++ as currently standardized. In C++0x, you'll be able to use template parameter packs to approximate (I think I've got the syntax right, but not sure about expansion in requires):

template <class ValType, int Rank>
struct NDimensionalArray
{
    template <class... Args>
    requires std::SameType<Args, ValType>... && std::True<sizeof...(Args) == Rank>
    NDimensionalArray(Args... values)
    {
        ...
    }
};
Pavel Minaev
Sounds like a cool feature, the syntax is a little beyond me without a simpler example ahah, but cool nonetheless.
DeusAduro
A: 

Boost has a multi-array library that uses a custom object for constructing their multidimensional array. It's a really good way to do it; I suggest you study (or better yet, use) their code.

rlbond
Interesting stuff. I could just use their class of course, but implementing this kind of thing is always educational. I have something working with va_list ( ... ), but it seems... fragile? Not really sure the word just doesn't seem very robust.
DeusAduro
Any solution that uses C varargs is not type-safe at all, as there's no way to restrict type or amount of arguments passed to the function, and no way to validate it for correctness at runtime either.
Pavel Minaev
+4  A: 

Okay, I've played with this for a while. Here's some template metaprogramming hackery that does something close to what you want. It lets you specify all dimensions inline, it doesn't do any dynamic memory allocation or other such things. In addition, with a good C++ compiler (I tested with VC++ /O2 option), the code will be fully inlined, with no copies done (in fact, for me it inlined the whole NDimensionalArray constructor at the point of the call). It will typecheck completely at compile-time, and won't let you pass too few or too many dimensions. And it can be reused for indexers. Here goes:

template<class T, int N>
class value_pack : private value_pack<T, N-1>
{
public:

 enum { size = N };

 value_pack(value_pack<T, N-1> head, const T& tail)
  : value_pack<T, N-1>(head)
  , value(tail)
 {
 }

 value_pack<T, N+1> operator() (const T& tail) const
 {
  return value_pack<T, N+1>(*this, tail);
 }

 template<int I>
 const T& get() const
 {
  return this->value_pack<T, I+1>::value;
 }

protected:

 const T value;
};

template<class T>
struct value_pack<T, 0>
{
};

struct
{
 template <class T>
 value_pack<T, 1> operator() (const T& tail) const
 {
  return value_pack<T, 1>(value_pack<T, 0>(), tail);
 }
} const values;


template <class ValType, int Rank>
struct NDimensionalArray
{
 NDimensionalArray(value_pack<ValType, Rank> values)
 {
  // ...
 }
};


int main()
{
 NDimensionalArray<int, 3> a(values(1)(2)(3));
}
Pavel Minaev
+1 very tricky sir. recursive template programming, could definitely work and may well be the fastest solution.
DeusAduro