Multidimensional arrays are not supported in standard C++, so they are implemented here via its own classes.
In Fish, they are recursively defined, such that algorithms on multidimensional arrays can be implemented as an induction:
Memory is stored using std::vector's. In addition to std::vector, Fish provides reference-counted memory management and dynamic type recognition as provided by the MemCore library.
In the following code, we will use T as an abstract type, over which templates and algorithms will be instantiated. T can be double, int, float, or whatever native or user-defined type. Constraints on this type are that std::vector<T> is possible. We assume familiarity with std::vector here.
The lowest level of data storage above the stl::vector class is a Chunk. Chunks are basically reference-counted std::vector's (see MemCore::RefPtr ), but also provide inheritance relationships.
Chunks may be stored type-independent in via their common base class ChunkBase, and recovered for a specific type:
RefPtr<Chunk<T> > MyChunk = new Chunk<T>(1000); RefPtr<ChunkBase> Data = MyChunk; if (RefPtr<TypedChunk<T> > MyData = Data) { std::vector<T>&vec = MyData->std_vector(); }
Chunks allow to discover inheritance relationships between the types it work on. For this functionality, one should always use TypedChunk<T> to recover the type of a ChunkBase instead of casting it to a Chunk<T> directly. A use case scenario is as follows:
struct Base { int i; }; struct Child : Base { };
So we have an inheritance relationship between Base and Child. Note that class Child does not add any new members, but might only provide additional operators. The array typecasting will not work if new data members are added. In addition to the classes, a type trait needs to be defined telling the inheritance relationship:
template <> struct BaseClass<Child> { typedef Base result; };
Now, the following code will work:
RefPtr<ChunkBase> Data = new Chunk<Child>(1000); if (RefPtr<TypedChunk<Child> > MyData = Data) { std::vector<Child>&vec = MyData->std_vector(); } if (RefPtr<TypedChunk<Base> > MyData = Data) { std::vector<Base>&vec = MyData->std_vector(); }
In other words, an array Chunk<Child> will be recognizeable as a Chunk<Base> and can be accessed as a std::vector<Base>, same as a Child* can be accessed as Base* in native C.
For convenience, a template MemVector<T> exists which is basically a RefPtr<Chunk<T> >, but provides the same member functions like a std::vector<T> .
The class MultiArray is the multdimensional equivalent to a std::vector. while a std::vector only allows one-dimensional indexing, a MultiArray is indexed with a multidimensional index, as provided by the class MultiIndex<N>, where N is the dimension. Note that the function MIndex() returns an MultiIndex<N> where N is the number of arguments given to the function.
T data[32*32*32]; // linear data array MultiArray<3,T> MyArray(data, MIndex(32,32,32) ); MyArray[ MIndex(15, 15, 15) ] = T();
A MultiArray does not provide any memory management or storage itself. It only refers to a given memory location and adds the multidimensional indexing scheme to it. One-dimensional MultiArrays are pretty pointless, but useful when implementing dimension-independent algorithms that shall work in arbitrary dimensions, including one. Within this usage sceneario, one-dimensional MultiArrays provide the same API as n-dimensional MultiArrays.
The class MultiArray internally employs the Iterator class that has support for different memory layouts of compound types. Compound types are types that are structs consisting of more than one member. A typical case is a point in physical space:
Creating arrays of compound data types is a no-brainer operation, a MultiArray<3,point> will just work. However, a desired functionality is to for example access the x-component of such an array as an array of floats, like a MultiArray<3,float>. The Iterator class allows to access a component of a compound array as if it were just the compound itself. The class MultiArray internally uses Iterators for this purpose.
Example is pending. Will be provided on demand, see source code in the meantime.
A common base class to all multidimensional arrays with memory management is class MemBase. If arrays of unknown type and property are to be stored somewhere, a RefPtr<MemBase> is the recommended type. The MemBase class provides various virtual functions that provide metadata about the given array.
Given an abstract MemBase, class TypedArray provides type information, independent from the array's dimensionality:
void f(const RefPtr<MemBase>&data) { if (RefPtr<TypedArray<T> > mydata = data) { if (CreativeIterator<T>*Cit = mydata->creativeIterator() ) { for(index_t i=0; i<Cit->count(); i++) { (*Cit)[ i ] = T(); } } } }
The convenience class TypedIterator provides a shortcut, allowing to eliminate one if-condition:
void f(const RefPtr<MemBase>&data) { if (TypedIterator<T> > mydata = data) { CreativeIterator<T>&C = *mydata; for(index_t i=0; i<C.count(); i++) { C[ i ] = T(); } { }
Independently from the type of an Array's element, one can query its dimensionality:
void f(const RefPtr<MemBase>&data) { if (RefPtr<MemArrayBase<3> > mydata = data) { MultIndex<3> Elements = mydata->Size(); } }
If you have given a MemBase and want to operate on a multidimensional array with a specific type, then query it to a ReferencingMemArray. This class is derived from a MultiArray, which contains the actual multidimensional indexing operations. Both readonly and write operations are possible.
void f(const RefPtr<MemBase>&data) { if (RefPtr<ReferencingMemArray<3,T> > mydata = data) { MultiArray<3,T>&M= *mydata; } }
Note that in the case of write operations any metadata associated with the array such as min/max may become invalid. It depends on the context where such metadata is stored, it might be well outside the MemArray.
When creating a multidimensional array, create objects of type MemArray, such as in:
RefPtr<MemBase> CreateArray(); { return new MemArray<3,T>( MIndex(32,32,32) ); }
A MemArray always contains exclusive memory, in contrast to the ReferencingMemArray, which may share its memory with another ReferencingMemArray. A MemArray is derived from a ReferencingMemArray.