views:

108

answers:

4

Hi everybody!

Edit: Note that my final purpose here is not having the class working, is just learning more about templates :-)

Suppose you have a template class which implements a vector:

template <typename T>
class Vector
{
    public:
        Vector(size_t dim) {
            dimension = dim;
            elements = new T[dim];
        }
        /* Here more stuff, like operator[] etc... */
    private:
        size_t dimension;
        T * elements;
}

And suppose you want to build a matrix with it. A matrix is just a vector of vectors, thus it can be designed as follows:

template <typename T>
class Matrix : public Vector<Vector<T> >
{
    /*...*/
}

And here comes trouble: In the constructor I need to provide rows and columns as parameter to the internal vectors. It should be something like

template <typename T>
Matrix<T>::Matrix (size_t ncols, size_t nrows)
         : Vector<Vector<T> > /* Here I need to specify size for both
                               * internal and external vectors */
{
}

Obviously I cannot write Vector<Vector<T>(nrows)>(ncols), but that's what I would need!

A possible solution would be including size inside the template:

template <typename T, size_t N>
class Vector
{
    public:
        Vector() {
            elements = new T[N];
        }
        /* Here more stuff, like operator[] etc... */
    private:
        T * elements;
}

Hence I would no longer need constructor parameters, but this also forces me to write clumsy code with templates everywhere (by exmample, every function using a Vector should be declared as

template <typename T, size_t N>
void foo (Vector<T,N> &vec) {...}

Do you have better solutions?

EDIT:

As solution I took inspiration from Mr Fooz's and chubsdad's posts. That's how I fixed the problem:

/* The RowAccess class is just an array wrapper which raises an exception
 * if you go out of bounds */
template <typename T>
class RowAccess
{
    public:

        RowAccess (T * values, unsigned cols) : values(vals), cols(c) {}

        T & operator[] (unsigned i) throw (MatrixError) {
            if (i < cols) return values[i];
            else throw MatrixError("Column out of bound");
        }

    private:
        T * values;
        unsigned cols;
};

template <typename T>
class Matrix
{
    public:
        Matrix (unsigned rows, unsigned cols) {...}
        virtual ~Matrix () {...}

        RowAccess<T> operator[] (unsigned row) {
            if (row < rows) return RowAccess<T>(values + cols * row, cols);
            else throw MatrixError("Row out of boundary");
        }

    private:
        unsigned rows;
        unsigned cols;
        T * values;
};

Thanks a lot to everyone!

A: 

It depends on what you expect from your Vector (and Matrix) class.

Either you want the size to be determined at runtime, and in that case, I'd suggest adding a resize() function which would allow you to size the Vector in the constructor as you like.

template <typename T>
class Vector
{
    public:
        Vector(size_t dim) {
            dimension = dim;
            elements = new T[dim];
        }
        Vector() : dimension(0), elements(0) {} // you need default constructor
        void resize(size_t dim) { // note: this could be implemented a lot better
          T* new_elements=new T[dim];
          for(int i=0; i<dim && i<dimension; i++)
            new_elements[i]=elements[i];
          delete [] elements;
          elements=new_elements; dimension=dim;
        }
        /* Here more stuff, like operator[] etc... */
    private:
        size_t dimension;
        T * elements;
}

You would then resize your Vectors in the Matrix constructor in a loop.

If you want the size of your vector or matrix to be determined at compile time, the best thing probably would be to use the template non-type argument as you suggested.

jpalecek
This may be a good solution. I also tought about it. The problem is that it allows the user to create an empty vector/matrix, thus makes the library weaker and more error-prone.
Dacav
+2  A: 

Use placement-new like this (burried behind the uninitialized_fill call)

template <typename T>
class Vector
{
    public:
        Vector(size_t dim, T const& c = T()) {
            dimension = dim;
            elements =
              static_cast<T*>(operator new(sizeof(T) * dim));
            std::uninitialized_fill(elements, elements + dim, c);
        }
        /* Here more stuff, like operator[] etc... */
    private:
        size_t dimension;
        T * elements;
};

Then you can call the constructor with Matrix::Vector(ncols, Vector<T>(nrows)) (you don't need to repeat the argument for the outer Vector, because Vector refers to Vector< Vector<T> > automatically since you inherit from the outer Vector. You need to make sure to call destructors manually before doing operator delete(elements) in the destructor then.

You might also want to embed the vector as a member, which i would probably prefer because I imagine not necessarily all operations of the outer vector make sense for a matrix. Then the initialization looks like m(ncols, Vector<T>(nrows)).


It should be noted that std::vector can also be used for this

template <typename T>
class Vector
{
    public:
        Vector(size_t dim, T const& c = T()):elements(dim, c)
        { }
    private:
        std::vector<T> elements;
};

This is an easy and safe way to accomplish that, and you get automatic memory management.

Johannes Schaub - litb
Basically you are using `operator new(sizeof(T) * dim)` instead of `new T[dim]` in order to avoid the constructor to be called?
Dacav
...Also is it legal to call the super constructor as `Matrix::Vector(...)`?
Dacav
@Dacav you need to call it that way. If you just say `Vector(...)` then the name will not be looked up in the base-class, because it depends on a template parameter. It's completely legal to name the base-class type in any way you see fit. You don't need `typename` here, because like with base-clause class-names, it's implied that you are naming a type.
Johannes Schaub - litb
@Johannes: Sorry to ask here but did you see what happened to [Neil Butterworth's](http://stackoverflow.com/users/69307/nb69307) account?
Prasoon Saurav
@Prasoon wait, WTF. How did he lose all his points? What happened to the guy.
Johannes Schaub - litb
@Johannes : I have asked the related question [here](http://meta.stackoverflow.com/questions/61077/why-does-neil-butterworth-say-please-delete-me). :-)
Prasoon Saurav
@Johannes Schaub: I appreciate your answer for the wizardry (+1), but I think that asking the user to avoid the constructor is weird and counter intuitive.
Dacav
+3  A: 

In OO terms, I would vote for "has" a relationship between Matrix and a Vector. A Matrix has vectors, rather than a Matrix "is a" vector, which means that Matrix should not derive from Vector.

EDIT 1: A small correction. "..which means that Matrix should not derive "publicly" from Vector". Private inheritance may be still fine.

Chubsdad
This is indeed a good idea (simple is good!), but requires me to redefine twice similar methods (like the operator[] one)
Dacav
@Dacav: Inheritance and composition are NOT the same thing, and inheritance is NOT for code re-use. You open yourself up to undefined behaviour by allowing an implicit cast to vector<vector<T>>, as it does not have a virtual destructor. When you have to define your own operator[] for the Matrix class, it is a one-liner to forward it to the underlying vector.
DeadMG
@DeadMG: Well, this is a wise point of view (+1 at your comment). However I'm trying to learn how to deal with this kind of situation (namely the one I described in my question) which happens with inheritance.
Dacav
I took your advice, see answer updated.
Dacav
+2  A: 

This isn't what you asked, but there's a good chance the matrix would be better of implemented as a single linear vector where you provide high-level access methods that do the indexing (e.g. elmLoc=row*ncols+col). This way you don't need to create and initialize a vector of vectors. You also don't need to worry about accidentally having some inner vectors of the differing size. All dense matrix implementations I have ever used use a single linear vector as the underlying implementation.

Mr Fooz
Good suggestion, see answer update.
Dacav