DEV Community

Cover image for [C++]Modelling a 2D-Array of Multiple Types
Hermes
Hermes

Posted on

[C++]Modelling a 2D-Array of Multiple Types

In my previous article(which i recommend you read before this) I modeled a 1-D array that can hold multiple types, today i'm going a step further to model a 2D-Array of multiple types using the same technique i used before which is templates.
I want my 2D-Array to have a :

.at() method which takes two numbers(row, column) as input and returns the value at that position.

.fill() method which replaces the values in all rows/column to what the user specified(the value specified is dependent on the type the array is modelling).

.swap() method which swaps the values of two 2D-Arrays.

.size() method which returns the size of the array.

This is what it would look like:

#include <iostream>
template<typename T, size_t R, size_t C>
class Array_2D{
    T arr[R][C];
    size_t row{R};
    size_t column{C};
    friend std::ostream &operator<<(std::ostream &os, Array_2D<T, R, C> &obb){

        for(size_t i{0}; i < obb.row; ++i){
            os << "[ ";
            for(size_t j{0}; j < obb.column; ++j){
                os << obb.at(i,j) << " "; 
            }
            os << "]\n";
        }
        return os;
    }
public:
    Array_2D(){
        T obj;
        fill(obj);
    }
    Array_2D(T obj){
        fill(obj);
    }
    Array_2D(Array_2D &obj){
        for(size_t i{0}; i < obj.row; ++i){
            for(size_t j{0}; j < obj.column; ++j){
                this->at(i,j) = obj.arr[i][j];
            }
        }
    }
    size_t size(){
        return row*column;
    }
    T& at(size_t a, size_t b){
        return arr[a][b];
    }
    void fill(T fil){
        for(size_t i{0}; i < this->row; ++i){
            for(size_t j{0}; j < this->column; ++j){
                arr[i][j] = fil;
            }
        }
    }

    void swap(Array_2D &obj){
        Array_2D<T, R, C> temp{obj};
        obj = *this;
        *this = temp;
    }
};
Enter fullscreen mode Exit fullscreen mode

I've overloaded the stream insertion operator to make it easy for me to output my 'user-defined class'(Array_2D).

I create a template which has three template parameter:

typename T represents the type the array will model which the user will specify when creating a Array_2D object.

size_t R and size_t C which represents the numbers of rows\column in the array, size_t is an unsigned type meaning it can only have values less then zero which is perfect in this case because obviously the rows/column can't be negative.

I then create the class with 3 data members:

T arr[R][C] which represents the actual 2D-Array of type T and row/column R/C.

size_t row/ size_t column which represents the rows and column for when i need to use the row/column in a class-method/function because i can't use the template parameters for that.

I have a no-args and args constructor, which call the fill() method that replaces all the values with the default values of the type the array is modelling(no-args constructor) or what the user specified(args-constructor). I have a .size() method that returns the number of items the array can hold and a copy constructor that allows us to give the value of an Array_2D object to another easily :

image.png

I have a .at() method that expects two numbers of unsigned type, which returns the value by reference(in case it needs to be modified) of the value of the array at the specified indexes([a][b])

A .fill() method which expects an object of type T which is the same type of the elements that the array is modelling, it replaces all the values in the array to what the user passed into it.

A .swap() method which takes another '2D-Array' of the same type as the one on the left hand of the operator and swaps their values(They have to have the same number of row and column).

TEST CASES:

image.png

EXTRAS

If i wanted to take things a bit further and give the class the ability to have methods like .begin() and .end() which return iterators to the beginning and one past the last value in the array, then i would have to give the class the ability to use Iterators by writing an iterator class for my Array_2D class, it would look like this:

#include <iostream>
#include <iterator>
#include <cstddef>
template<typename T, size_t R, size_t C>
class Array_2D{
    T arr[R][C];
    size_t row{R};
    size_t column{C};
    friend std::ostream &operator<<(std::ostream &os, Array_2D<T, R, C> &obb){

        for(size_t i{0}; i < obb.row; ++i){
            os << "[ ";
            for(size_t j{0}; j < obb.column; ++j){
                os << obb.at(i,j) << " "; 
            }
            os << "]\n";
        }
        return os;
    }
public:
////ITERATORS
    struct Iterator{
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type   = std::ptrdiff_t;
        using value_type        = T;
        using pointer           = T*;
        using reference         = T&;

        Iterator(pointer ptr) : m_ptr(ptr) {}
        reference operator*() const { return *m_ptr; }
        pointer operator->() { return m_ptr; }

        Iterator& operator++() { 
            m_ptr++;
            return *this;
        }  

        Iterator operator++(int) {
            Iterator tmp = *this;
            ++(*this);
            return tmp; 
        }
        Iterator& operator--(){
            --m_ptr;
            return *this;
        }
        Iterator operator--(int){
            Iterator tmp = *this;
            --(*this);
            return tmp;
        }
        friend bool operator== (const Iterator& a, const Iterator& b) { return a.m_ptr == b.m_ptr; };
        friend bool operator!= (const Iterator& a, const Iterator& b) { return a.m_ptr != b.m_ptr; }; 
private:
    pointer m_ptr;
    };
////
    Iterator begin() { 
        return Iterator(&arr[0][0]);
    }
    Iterator end()   { 
        return Iterator(&arr[row-1][column-1]); 
    }
    Array_2D(){
        T obj;
        fill(obj);
    }
    Array_2D(T obj){
        fill(obj);
    }
    Array_2D(Array_2D &obj){
        for(size_t i{0}; i < obj.row; ++i){
            for(size_t j{0}; j < obj.column; ++j){
                this->at(i,j) = obj.arr[i][j];
            }
        }
    }
    size_t size(){
        return row*column;
    }
    T& at(size_t a, size_t b){ 
        return arr[a][b];
    }
    void fill(T fil){
        for(size_t i{0}; i < this->row; ++i){
            for(size_t j{0}; j < this->column; ++j){
                arr[i][j] = fil;
            }
        }
    }

    void swap(Array_2D &obj){
        Array_2D<T, R, C> temp{obj};
        obj = *this;
        *this = temp;
    }
};
Enter fullscreen mode Exit fullscreen mode

image.png

But the .end() method that returns an iterator one past the last element isn't behaving properly so i didn't add this iterator part to the main blogpost, this is the first time i'm adding iterators to my class so i haven't figured the ins and outs yet, you can see more on adding iterators to user defined class here, and you can come to the repository where i uploaded this 2D-Array class here in case you want to help me figure this stuff out. Thanks

Discussion (0)