2

I have this matrix class which has a 2d double array. In the constructor you can specify the width and height. I want to create a 1d array instead of a 2d when the width is 1. Because I overloaded the [] operator and return the pointer. If there is only 1 row/col I don't want to always write [i][0]. Instead I want to just write [i]. Does anyone know how to solve this?

Edit: To clarify this I need this class for matrix calculations no just as array.

  • 1
    If your matrix size is determined at runtime, then there is no way the compiler can know whether the width is 1 or not. One possibility is to have a different class for this case. – Vaughn Cato Dec 28 '16 at 22:11
  • 2
    You will need to create a second class that represents a 1D array. Even better, use `std::vector >` to represent a 2D array - no messing about with managing array dimensions, memory allocation, or trying to overload (as you are) `operator[]()` in strange ways. – Peter Dec 28 '16 at 22:13
  • Do you need to be able to dynamically resize your matrix? If not, you can take the rows and columns as template parameters, which would let you specialise in the case that either is `1`. – Justin Time - Reinstate Monica Dec 28 '16 at 22:33
  • @JustinTime No. –  Dec 28 '16 at 22:34

5 Answers5

2

You can wrap the two alternative types into a variant type (a tagged union).

However, you cannot use operator[] to access both variants, since the return type would be different for each. One would return a reference to a subarray, while the other would return a double. You cannot have two overloads that differ only by their return type.

Instead, you can use overloaded functions. For example, double at(size_type x) and double at(size_type x, size_type y).


However, since you're representing a matrix, it might be simpler to use a 1D array to represent a matrix of any arbitrary rank by laying the higher dimensions flat consecutively, just like multidimensional arrays are stored in memory (memory is unidimensional, after all). This allows you to specify the width of each dimension at runtime and you avoid the complexity of the variant type.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

A nice solution would be to use std::variant, or boost::variant if you don't have a C++17 compiler. I would create a container that ease it's use.

template<typename T>
struct DynamicDimension {
    std::variant<T, std::vector<T>> element;

    // accessors, is_vector and is_element function.
    // Maybe operator[] and push_back and a get function.
    // Add begin and end to make your class useable with range for loops.
};
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
0

This question really interested me, and in my original answer, I stated that the desired operation wasn't possible because in C++ you can't overload functions which only differ in their return type.

But what if you could?

I became curious, so I spent quite a lot of my time researching and fiddling with C++ to see if I could, in fact, make one function return multiple types... And the answer it 'yes,' but it's a way not generally used because it uses void *, which makes life difficult.

It did, however, have the added perk of writing fewer lines of code, even if harder to understand.

That said, I learned something from the experience, so I thought I should share.

And so, there is different way to solve this with less code. In the matrix class, have two pointers, one a T * matrix and the other a T ** matrix2d, and use a void * as a return type for your overloaded [] operator.

#ifndef matrix_h
#define matrix_h

template <typename T>
class Matrix {
private:
    int width;
    int height;
    int size;
    T * matrix;
    T ** matrix2d;

public:
    Matrix(const int w, const int h): width(w), height(h){
        if(w==1){
            matrix = new T[h];
            size = h;
        } else if (h==1){
            matrix = new T[w];
            size = w;
        } else {
            matrix2d = new T*[h];
            for(int i=0;i<h;++i){
                matrix2d[i] = new T[w];
            }
            size = w*h;
        }
    }

    ~Matrix() {
        if(width==1 || height==1){
            delete [] matrix;
        } else {
            for(int i=0;i<height;++i){
                T * _r = matrix2d[i];
                delete [] _r;
            }
            delete [] matrix2d;
        }
    }

    void * operator[](const int i){
        if(width==1 || height==1){
            return & matrix[i];
        } else {
            return & (*matrix2d[i]);
        }
    }

    const int getSize(){
        return size;
    }
};

#endif /* matrix_h */

In main, do this to demo:

#include <iostream>
#include "Matrix.h"

int main() {
    //Give the type so the correct size_t is allocated in memory.
    Matrix <double> matrix1(1, 3);
    Matrix <double> matrix2(2,2);

    //Now have an array of void pointers.
    std::cout << "1 dimensional" << std::endl;
    for(int i=0;i<matrix1.getSize();++i){
        std::cout << matrix1[i] << std::endl;
    }

    //Cast the void *, then dereference it to store values.
    *((double*)matrix1[0]) = 77;
    *((double*)matrix1[1]) = 31;
    *((double*)matrix1[2]) = 24.1;

    for(int i=0;i<matrix1.getSize();++i){
        std::cout << *((double *)matrix1[i]) << std::endl;
    }

    std::cout << "2 dimensional addresses." << std::endl;
    for(int i=0;i<2;++i){
        double * _row = (double*)matrix2[i];
        for(int j=0;j<2;++j){
            std::cout << &_row[j] << " ";
        }
        std::cout << std::endl;
    }

    std::cout << "2 dimensional assignment and display." << std::endl;
    double num = 13.1;
    for(int i=0;i<2;++i){
        double * _row = (double*)matrix2[i];
        for(int j=0;j<2;++j){
            _row[j] = num;
            num += 0.13;
            std::cout << _row[j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}
NonCreature0714
  • 5,744
  • 10
  • 30
  • 52
0

I would recommend using a one-dimensional array, as user2079303 suggested in their answer. If this isn't desirable, however, it's also possible to implement your idea with either templates or polymorphism.


If you don't need to be able to change its dimensions on the fly, you could make your matrix a templated class, using its dimensions as template parameters. This, in turn, allows the dimensions to be used for compile-time logic with SFINAE, which allows you to overload operator[]() based on the matrix's dimensions.

Note that this doesn't actually create a 1D array when Rows == 1 or Cols == 1, it merely simulates one; if you explicitly need that behaviour, instead of technically different but mostly equivalent behaviour, you'll want to look into specialising the class for when one or both parameters is 1. This will likely involve duplicating at least some of the code, so it'll be a bit messy.

// This example uses std::array for the actual array, since it's compile-time anyways.
// This, in turn, lets it declare a lot of functions constexpr.
// Const-correctness omitted for brevity.  Remember to restore it for actual code.
template<size_t Rows, size_t Cols>
class Matrix {
    std::array<std::array<double, Cols>, Rows> mat;

  public:
    // Default constructor. Clang _really_ likes braced initialiser lists.
    constexpr Matrix() : mat{{{{0}}}} {}

    // Array constructor.
    constexpr Matrix(const decltype(mat)& arr) : mat(arr) {}

    // -----

    // Subscript operators.

    // Generic operator.  Matrix<x, y>, where x != 1 && y != 1.
    // Does what normal subscript operators do.
    template<bool R = (Rows == 1), bool C = (Cols == 1)>
    auto& operator[](std::enable_if_t<!R && !C, size_t> i) {
        return mat[i];
    }

    // Magic operator.  Matrix<1, x>, where x != 1.
    template<bool R = (Rows == 1), bool C = (Cols == 1)>
    auto& operator[](std::enable_if_t<(R && !C), size_t> i) {
        return mat[0][i];
    }

    // Magic operator.  Matrix<x, 1>, where x != 1.
    template<bool R = (Rows == 1), bool C = (Cols == 1)>
    auto& operator[](std::enable_if_t<C && !R, size_t> i) {
        return mat[i][0];
    }

    // Scalar matrix operator.  Matrix<1, 1>.
    // Just returns mat[0][0], for simplicity's sake.  Might want to make it do something
    //  more complex in your actual class.
    template<bool R = (Rows == 1), bool C = (Cols == 1)>
    auto& operator[](std::enable_if_t<R && C, size_t> i) {
        return mat[0][0];
    }

    // -----

    // A few interface helpers.

    // Simple begin() & end(), for example's sake.
    // A better version would begin at mat[0][0] and end at mat[Rows - 1][Cols - 1].
    constexpr auto begin() const { return mat.begin(); }
    constexpr auto   end() const { return mat.end(); }

    // Generic helpers.
    constexpr size_t    size() const { return mat.size() * mat[0].size(); }
    constexpr size_t    rows() const { return mat.size(); }
    constexpr size_t    cols() const { return mat[0].size(); }

    // 1D Matrix helpers.
    constexpr bool    is_one_d() const { return (Rows == 1) || (Cols == 1); }
    constexpr bool     one_row() const { return Rows == 1; }
    constexpr size_t dimension() const { return (one_row() ? cols() : rows()); }

    // -----

    // Output.
    // Would need modification if better begin() & end() are implemented.
    friend std::ostream& operator<<(std::ostream& str, const Matrix<Rows, Cols>& m) {
        for (auto& row : m) {
            for (auto& elem : row) {
                str << std::setw(6) << elem << ' ';
            }
            str << '\n';
        }
        str << std::endl;
        return str;
    }
};

It can be used as...

// Get rid of any "Waah, you didn't use that!" warnings.
// See https://stackoverflow.com/a/31654792/5386374
#define UNUSED(x) [&x]{}()

// This should really use if constexpr, but online compilers don't really support it yet.
// Instead, have an SFINAE dummy.
template<size_t Rows, size_t Cols>
void fill(Matrix<Rows, Cols>& m, std::enable_if_t<(Rows == 1) || (Cols == 1), int> dummy = 0) {
    UNUSED(dummy);

    //for (size_t i = 0; i < (m.one_row() ? m.cols() : m.rows()); i++) {
    for (size_t i = 0; i < m.dimension(); i++) {
        m[i] = (i ? i : 0.5) * (i ? i : 0.5);
    }
}

template<size_t Rows, size_t Cols>
void fill(Matrix<Rows, Cols>& m, std::enable_if_t<!((Rows == 1) || (Cols == 1)), int> dummy = 0) {
    UNUSED(dummy);

    for (size_t i = 0; i < m.rows(); i++) {
        for (size_t j = 0; j < m.cols(); j++) {
            m[i][j] = (i ? i : 0.5) * (j ? j : 0.5) + (i >= j ? 0.1 : -0.2);
        }
    }
}

See it in action here.


If you do need to be able to change its dimensions on the fly, this becomes more complex. The easiest solution would probably be to use polymorphism, and have operator[] return a proxy.

class Matrix {
  protected:
    // Out proxy class.
    // Visible to children, for implementing.
    struct SubscriptProxy {
        virtual operator double&() = 0;
        virtual operator double*() = 0;
        virtual double& operator=(double) = 0;
        virtual double& operator[](size_t) = 0;
        virtual ~SubscriptProxy() = default;
    };

  public:
    virtual SubscriptProxy& operator[](size_t i) = 0;
    virtual ~Matrix() = default;

    virtual void out(std::ostream& str) const = 0;
    friend std::ostream& operator<<(std::ostream& str, const Matrix& m) {
        m.out(str);
        return str;
    }
};
std::ostream& operator<<(std::ostream& str, const Matrix& m);

You can then have each class make the proxy members it needs public, and the ones it doesn't private; the private ones get a non-functional dummy implementation.

// Resizing omitted for brevity.
class OneDMatrix : public Matrix {
    double arr[5];

    // Proxy for single element.
    class OneDProxy : public SubscriptProxy {
        double& elem;

        operator double*() override { return &elem; }
        double& operator[](size_t) override { return elem; }
      public:
        OneDProxy(double& e) : elem(e) {}

        operator double&() override { return elem; }
        double& operator=(double d) override {
            elem = d;
            return elem;
        }
    };

  public:
    OneDMatrix() : arr{0} {}

    // operator[] maintains a static pointer, to keep the return value alive and guarantee
    //  proper cleanup.
    SubscriptProxy& operator[](size_t i) override {
        static OneDProxy* ret = nullptr;

        if (ret) { delete ret; }
        ret = new OneDProxy(arr[i]);
        return *ret;
    }

    void out(std::ostream& str) const override {
        for (size_t i = 0; i < 5; i++) {
            str << std::setw(4) << arr[i] << ' ';
        }
        str << std::endl;
    }
};

// Resizing omitted for brevity.
class TwoDMatrix : public Matrix {
    double arr[3][4];

    // Proxy for array.
    class TwoDProxy : public SubscriptProxy {
        double* elem;

        operator double&() override { return elem[0]; }
        double& operator=(double) override { return elem[0]; }
      public:
        TwoDProxy(double* e) : elem(e) {}
        operator double*() override { return elem; }
        double& operator[](size_t i) override { return elem[i]; }
    };

  public:
    TwoDMatrix() : arr{{0}} {}

    // operator[] maintains a static pointer, to keep the return value alive and guarantee
    //  proper cleanup.
    SubscriptProxy& operator[](size_t i) override {
        static TwoDProxy* ret = nullptr;

        if (ret) { delete ret; }
        ret = new TwoDProxy(arr[i]);
        return *ret;
    }

    void out(std::ostream& str) const override {
        for (size_t i = 0; i < 3; i++) {
            for (size_t j = 0; j < 4; j++) {
                str << std::setw(4) << arr[i][j] << ' ';
            }
            str << '\n';
        }
    }
};

Due to references being usable for polymorphism, Matrix can be used as an interface, allowing anything that doesn't specifically require one of the actual classes to take a Matrix&.

See it in action here.

With a setup like this, it becomes easy to dynamically create the right kind of matrix, by providing a helper function.

// Assume OneDMatrix is expanded for dynamic size specification.
  // It now has constructor OneDMatrix(size_t sz), and owns a dynamic double[sz].
// Assume TwoDMatrix is expanded for dynamic size specification.
  // It now has constructor TwoDMatrix(size_t r, size_t c), and owns a dynamic double[r][c].
Matrix* createMatrix(size_t rows, size_t cols) {
    if (rows == 1)      { return new OneDMatrix(cols);       }
    else if (cols == 1) { return new OneDMatrix(rows);       }
    else                { return new TwoDMatrix(rows, cols); }
}

As usual, the template version is more verbose, but safer (due to not having to play around with pointers) and likely more efficient (due to not needing to do as much dynamic allocation & deallocation; I haven't tested this, however).

Community
  • 1
  • 1
-1

To make a 2D dynamic array you need to use some Data Structures concepts and I am considering that you not familiar with them. Before that, let me show you how make a 1D dynamic array.

int main()
{
  int size;

  std::cin >> size;

  int *array = new int[size];

  delete [] array;

  return 0;
}

Don't forget to delete every array you allocate with new.

Let's go back to 2D dynamic arrays. These arrays can be summarized as a hash table which is a common type of data structure. Now this person has explained 2D arrays much better than me How to create dynamic 2D array follow the link.

Community
  • 1
  • 1