I've used this pattern several times when creating a class templates of a matrix class with variable dimensions.
Matrix.h
#ifndef MATRIX_H
template<typename Type, size_t... Dims>
class Matrix {
public:
static const size_t numDims_ = sizeof...(Dims);
private:
size_t numElements_;
std::vector<Type> elements_;
std::vector<size_t> strides_; // Technically this vector contains the size of each dimension... (its shape)
// actual strides would be the width in memory of each element to that dimension of the container.
// A better name for this container would be dimensionSizes_ or shape_
public:
Matrix() noexcept;
template<typename... Arg>
Matrix( Arg&&... as ) noexcept;
const Type& operator[]( size_t idx ) const;
size_t numElements() const {
return elements_.size();
}
const std::vector<size_t>& strides() const {
return strides_;
}
const std::vector<Type>& elements() const {
return elements_;
}
}; // matrix
#include "Matrix.inl"
#endif // MATRIX_H
Matrix.inl
template<typename Type, size_t... Dims>
Matrix<Type, Dims...>::Matrix() noexcept :
strides_( { Dims... } ) {
using std::begin;
using std::end;
auto mult = std::accumulate( begin( strides_ ), end( strides_ ), 1, std::multiplies<>() );
numElements_ = mult;
elements_.resize( numElements_ );
} // Matrix
template<typename Type, size_t... Dims>
template<typename... Arg>
Matrix<Type, Dims...>::Matrix( Arg&&... as ) noexcept :
elements_( { as... } ),
strides_( { Dims... } ){
numElements_ = elements_.size();
} // Matrix
template<typename T, size_t... d>
const T& Matrix<T,d...>::operator[]( size_t idx ) const {
return elements_[idx];
} // Operator[]
Matrix.cpp
#include "Matrix.h"
#include <vector>
#include <numeric>
#include <functional>
#include <algorithm>
main.cpp
#include <vector>
#include <iostream>
#include "matrix.h"
int main() {
Matrix<int, 3, 3> mat3x3( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
for ( size_t idx = 0; idx < mat3x3.numElements(); idx++ ) {
std::cout << mat3x3.elements()[idx] << " ";
}
std::cout << "\n\nnow using array index operator\n\n";
for ( size_t idx = 0; idx < mat3x3.numElements(); idx++ ) {
std::cout << mat3x3[idx] << " ";
}
std::cout << "\n\ncheck the strides\n\n";
for ( size_t idx = 0; idx < mat3x3.numDims_; idx++ ) {
std::cout << mat3x3.strides()[idx] << " ";
}
std::cout << "\n\n";
std::cout << "=================================\n\n";
Matrix<float, 5, 2, 9, 7> mf5x2x9x7;
// Check Strides
// Num Elements
// Total Size
std::cout << "The total number of dimensions are: " << mf5x2x9x7.numDims_ << "\n";
std::cout << "The total number of elements are: " << mf5x2x9x7.numElements() << "\n";
std::cout << "These are the strides: \n";
for ( size_t n = 0; n < mf5x2x9x7.numDims_; n++ ) {
std::cout << mf5x2x9x7.strides()[n] << " ";
}
std::cout << "\n";
std::cout << "The elements are: ";
for ( size_t n = 0; n < mf5x2x9x7.numElements(); n++ ) {
std::cout << mf5x2x9x7[n] << " ";
}
std::cout << "\n";
std::cout << "\nPress any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
} // main
This is a simple variable multidimensional matrix class of the Same Type <T>
You can create a matrix of floats, ints, chars etc of varying sizes such as a 2x2
, 2x3
, 5x3x7
, 4x9x8x12x2x19
. This is a very simple but versatile class.
It is using std::vector<>
so the search time is linear. The larger the multi - dimensional matrix grows in dimensions the larger the internal container will grow depending on the size of each dimension; this can "explode" fairly quick if each individual dimensions are of a large dimensional size for example: a 9x9x9
is only a 3 dimensional volumetric matrix
that has many more elements than a 2x2x2x2x2
which is a 5 dimensional volumetric matrix
. The first matrix has 729
elements where the second matrix has only 32
elements.
I did not include a default constructor, copy constructor, move constructor, nor any overloaded constructors that would accept either a std::container<T>
or another Matrix<T,...>
. This can be done as an exercise for the OP.
I also did not include any simple functions that would give the size of total elements from the main container, nor the number of total dimensions which would be the size of the strides
container size. The OP should be able to implement these very simply.
As for the strides
and for indexing with multiple dimensional coordinates the OP would need to use the stride
values to compute the appropriate indexes again I leave this as the main exercise.
EDIT - I went ahead and added a default constructor, moved some members to private section of the class, and added a few access functions. I did this because I just wanted to demonstrate in the main function the power of this class even when creating an empty container of its type.
Even more you can take user Yakk's answer with his "stride & slice" algorithm and should easily be able to plug it right into this class giving you the full functionality of what you are looking for.