Search code examples
c++matrixdynamic-memory-allocationtemplate-argument-deduction

template argument deduction for a allocating Matrix


I have this Matrix class that allocates the data on the heap and a helper class M that is a Matrix with the data as C-style array, no allocation. Template argument deduction works for the helper class automatically. But not for the Matrix class.

I can construct a Matrix from that with template argument deduction:

M m{{{1, 2}, {3, 4}}};
Matrix a{M{{{1, 2}, {3, 4}}}};

What I'm looking for is to get rid of the helper class so the following works:

Matrix a{{{1, 2}, {3, 4}}};

Here is a working example with the helper class: https://godbolt.org/z/46vEqbvax

#include <cstddef>
#include <type_traits>
#include <cstring>
#include <initializer_list>
#include <utility>
#include <memory>
#include <cassert>
#include <algorithm>

template <typename T, std::size_t rows, std::size_t cols>
class M {
public:
    const T * operator[](std::size_t x) const {
        return data[x];
    }
    T * operator[](std::size_t x) {
        return data[x];
    }
    T data[rows][cols]{};
};

template <typename T, std::size_t rows, std::size_t cols>
class Matrix {
private:
    T *data{new T[rows * cols]};
public:
    Matrix() { }
    ~Matrix() {
        delete[] data;
        data = nullptr; // crash on use after free
    }
    Matrix(const Matrix &other) {
        *this = other;
    }
    Matrix(T &&other) : data(other.data) {
        other.data = nullptr;
    }
    Matrix & operator=(const Matrix &other) {
        if constexpr (std::is_aggregate_v<T>) {
            memcpy(data, other.data, sizeof(T) * rows * cols);
        } else {
            for (std::size_t i = 0; i < rows; ++i) {
                for (std::size_t j = 0; j < cols; ++j) {
                    (*this)[i][j] = other[i][j];
                }
            }
        }
        return *this;
    }
    Matrix operator=(Matrix &&other) {
        swap(data, other.data);
        return *this;
    }
    const T * operator[](std::size_t x) const {
        return &data[x * cols];
    }
    T * operator[](std::size_t x) {
        return &data[x * cols];
    }
    Matrix(const M<T, rows, cols>& other) {
        if constexpr (std::is_aggregate_v<T>) {
            memcpy(data, other.data, sizeof(T) * rows * cols);
        } else {
            for (std::size_t i = 0; i < rows; ++i) {
                for (std::size_t j = 0; j < cols; ++j) {
                    (*this)[i][j] = other[i][j];
                }
            }
        }
    }
    Matrix(M<T, rows, cols>&& other) {
        if constexpr (std::is_aggregate_v<T>) {
            memcpy(data, other.data, sizeof(T) * rows * cols);
        } else {
            for (std::size_t i = 0; i < rows; ++i) {
                for (std::size_t j = 0; j < cols; ++j) {
                    std::swap((*this)[i][j], other[i][j]);
                }
            }
        }
    }
};

//template <typename T, std::size_t rows, std::size_t cols>
//Matrix(M<T, rows, cols>) -> Matrix<T, rows, cols>;

#include <iostream>

int main() {
    Matrix a{M{{{1, 2}, {3, 4}}}};
    Matrix b{a};
    std::cout << b[0][0] << " " << b[0][1] << std::endl;
    std::cout << b[1][0] << " " << b[1][1] << std::endl;
}

Solution

  • You can get rid of helper class M and have your Matrix template parameters automatically deduced by changing the type of your constructor parameters to (l- and r-value references to) C-style arrays:

    //Matrix(const M<T, rows, cols>& other) {
    Matrix(const T (&other)[rows][cols]) {
        // ... same implementation ...
    }
    
    //Matrix(M<T, rows, cols>&& other) {
    Matrix(T (&&other)[rows][cols]) {
        // ... same implementation ...
    }
    

    Demo