Search code examples
c++eigen

How to control memory allocation depending on template parameters?


I am looking into the Eigen matrix class library and the docs state that a Matrix template class can be instantiated using the following template parameters:

Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>

where Scalar is the type of the Matrix coefficients (double, float, int, complex, etc...). RowsAtCompileTime and ColsAtCompileTime are the Matrix dimensions respectively.

For example, a 4x4 Matrix of floats could be instantiated as follows:

Matrix<float, 4, 4> matrix;

The Eigen docs state that in the previous case, because the rows are known at compile time, and for "small-sized" Matrices, memory for the Matrix elements is statically allocated.

However if you choose to allocate memory dynamically (for large matrices or because you don't know at compile time the number of rows and columns), you can use the following template instantiation, which will allocate memory dynamically:

Matrix<float, Dynamic, Dynamic> matrix;

My question is how is this achieved conceptually using the C++ template mechanism? Since I am mostly interested in the C++ techniques used for achieving this, could someone please give me a short example using a Stack type where the memory allocation is controlled in a similar way? Thanks!


Solution

  • The template will be partially specialised to handle the different allocations. In short,

    template <typename T, int N>
    struct Vector {
        T data[N];
    };
    
    static const int Dynamic = -1;
    
    template <typename T>
    struct Vector<T, Dynamic> {
        T* data;
    };
    

    Clearly a lot of the interface is missing in this example, but this highlights the important part of the mechanics of template specialisation, as well as the detail of the differing underlying storage. The constructor (in the specialisation) would perform the dynamic allocation.

    In practice, one might defer the details of the storage to a VectorStorage<T, Rows, Cols> which is partially specialised. The Vector would contain a member of that type (properly instantiated using its own parameters) and much of the interface would be implemented only once...

    template <typename T, int N>
    struct VectorStorage {
        VectorStorage(const size_t) {}
        T data[N];
    };
    
    static const int Dynamic = -1;
    
    template <typename T>
    struct VectorStorage<T, Dynamic> {
        VectorStorage(const size_t n) : data(new T[n]) {}
        ~VectorStorage() { delete[] data; }
        // define copy ops, etc.
        T* data;
    };
    
    template <typename T, int N>
    class Vector {
      public:
        Vector(const size_t n) : storage(n) {}
    
        T operator[](const size_t i) const { return storage.data[i]; }
        // etc.
    
      private:
        VectorStorage<T,N> storage;
    };