Example: Templated Array

With templates we can easily extend our contiguous array class to work with any data type. We will mark it as:

template <typename T>
class Array

and our member data will now be:

std::size_t _rows;
std::size_t _cols;
std::vector<T> _data;

Here’s a full implementation:

Listing 107 templated version of array.H
#ifndef ARRAY_H
#define ARRAY_H

#include <vector>
#include <iostream>
#include <cassert>
#include <limits>

///
/// a contiguous 2-d array
/// here the data is stored in row-major order in a 1-d memory space
/// managed as a vector.  We overload () to allow us to index this as
/// a(irow, icol)
///
template <typename T>
class Array {

private:

    std::size_t _rows;
    std::size_t _cols;
    std::vector<T> _data;

public:

    Array (std::size_t rows, std::size_t cols, T val=0.0)
        : _rows{rows},
          _cols{cols},
          _data(rows * cols, val)
    {
        // we do the asserts here after the initialization of _data
        // in the initialization list, but if the size is zero, we'll
        // abort here anyway.

        assert (rows > 0);
        assert (cols > 0);

    }

    // copy constructor
    Array (const Array& src) = default;

    // assignment operator
    Array& operator= (const Array& src) = default;

    // destructor
    ~Array() = default;

    // note the "const" after the argument list here -- this means
    // that this can be called on a const Array

    ///
    /// get the number of columns
    ///
    inline std::size_t ncols() const { return _cols;}

    ///
    /// get the number of rows
    ///
    inline std::size_t nrows() const { return _rows;}

    ///
    /// get the total number of elements in the array
    ///
    inline std::size_t nelements() const { return _cols * _rows;}

    inline T& operator()(int row, int col) {
        assert (row >= 0 && row < _rows);
        assert (col >= 0 && col < _cols);
        return _data[row*_cols + col];
    }

    inline const T& operator()(int row, int col) const {
        assert (row >= 0 && row < _rows);
        assert (col >= 0 && col < _cols);
        return _data[row*_cols + col];
    }

    ///
    /// return a flattened view of the data region -- this can be used with
    /// a range-for loop, e.g. `for (auto &e : a.flat()) {...}`
    ///
    inline std::vector<T>& flat() { return _data; }

    ///
    /// set an array element in the flattened index space
    ///
    inline T& flat(int element) {
        assert (element >= 0 && element < _rows * _cols);
        return _data[element];
    }

    ///
    /// get an array element in the flattened index space for
    /// a const array
    ///
    inline const T& flat(int element) const {
        assert (element >= 0 && element < _rows * _cols);
        return _data[element];
    }

    ///
    /// return the global minimum of the array
    ///
    T min() const {
        T arr_min{std::numeric_limits<T>::max()};
        for (const T& e : _data) {
            arr_min = std::min(arr_min, e);
        }
        return arr_min;
    }

    ///
    /// return the global maximum of the array
    ///
    T max() const {
        T arr_max{std::numeric_limits<T>::lowest()};
        for (const T& e : _data) {
            arr_max = std::max(arr_max, e);
        }
        return arr_max;
    }

};

// the << operator is not part of the of the class, so it is not a member

template <typename T>
inline
std::ostream& operator<< (std::ostream& os, const Array<T>& a) {

    for (std::size_t row=0; row < a.nrows(); ++row) {
        for (std::size_t col=0; col < a.ncols(); ++col) {
            os << a(row, col) << " ";
        }
        os << std::endl;
    }

    return os;
}

#endif

Now we can create arrays of different types like:

Array<double> a(10, 10);
Array<int> b(5, 10);

Here’s a test driver:

Listing 108 test_array.cpp
#include <iostream>

#include "array.H"

int main() {

    Array<double> x(5, 5);

    for (std::size_t row=0; row < x.nrows(); ++row) {
        for (std::size_t col=0; col < x.ncols(); ++col) {
            if (row == col) {
                x(row, col) = 1.5;
            }
        }
    }

    std::cout << x << std::endl;

    Array<int> y(5, 5);

    for (std::size_t row=0; row < y.nrows(); ++row) {
        for (std::size_t col=0; col < y.ncols(); ++col) {
            if (row == col) {
                y(row, col) = 1.5;
            }
        }
    }

    std::cout << y << std::endl;


}

try it…

Add another constructor to our class that creates a square array. It will only need to take a single dimension, N, which is then used to set both _rows and _cols.