Matrix class with overloaded `operator[]`
Pragma once

Instead of

#ifndef DP2_MATRIX_H
#define DP2_MATRIX_H


consider using

#pragma once


Once is non-standard, but supported by all compilers supporting
C++14 and reduces compilation times.

Includes

On MSVC, #include &lt;stdexcept&gt; is needed for the out_of_range
error. I also think your include lines look tidier if you sort them in
alphabetical order. :)

Bounds checking

Matrix&lt;int&gt;(10, 10, 0)[10][0]


Is an error that will be detected by your bounds checking, but

Matrix&lt;int&gt;(10, 10, 0)[0][10]


is the same type of error, but won't be detected. Consider following
the STL convention described in this Stackoverflow
answer. That
is, you remove bounds checking from operator[] but add a method at
which does bounds checking for both dimensions.

Const correctness

You should add const declared variants of the accessors. You can
also add it to the parameters, but imho, that is not as useful.

Result:

#pragma once

#include &lt;algorithm&gt;
#include &lt;cstdio&gt;
#include &lt;memory&gt;
#include &lt;stdexcept&gt;

template &lt;typename T&gt;
class Matrix {
private:
    size_t row{};
    size_t col{};
    std::unique_ptr&lt;T[]&gt; data;
    void boundscheck(int r, int c) const {
        if (!(0 &lt;= r &amp;&amp; r &lt; row &amp;&amp; 0 &lt;= c &amp;&amp; c &lt; col)) {
            throw std::out_of_range("Can not access out of bound element!");
        }
    }
public:
    // Set all the values of the Matrix
    void setValues(T value) {
        std::fill_n(data.get(), row*col, value);
    }
    explicit Matrix(size_t row, size_t col, T def) {
        // Create a matrix of row X col and initialize
        // each element of the matrix with def
        this-&gt;row = row;
        this-&gt;col = col;
        this-&gt;data = std::make_unique&lt;T[]&gt;(this-&gt;row * this-&gt;col);
        setValues(def);
    }
    // Overload the [] operator for the 2d array like access
    T* operator[](int r) {
        return &amp;this-&gt;data[r * col];
    }
    const T* operator[](int r) const {
        return &amp;this-&gt;data[r * col];
    }
    T&amp; at(int r, int c) {
        boundscheck(r, c);
        return this-&gt;data[r * col + c];
    }
    const T&amp; at(int r, int c) const {
        boundscheck(r, c);
        return this-&gt;data[r * col + c];
    }
    // Overload the &lt;&lt; operator for console logging
    friend std::ostream&amp; operator&lt;&lt; (std::ostream&amp; os, Matrix &amp;mObj) {
        auto shape = mObj.shape();
        for(int i=0; i&lt; shape.first; i++) {
            for(int j=0; j&lt;shape.second; j++) {
                os &lt;&lt; mObj[i][j] &lt;&lt; " ";
            }
            os &lt;&lt; "\n";
        }
        return os;
    }
    // Get row and col values
    std::pair&lt;size_t, size_t&gt; shape() const {
        return std::make_pair(this-&gt;row, this-&gt;col);
    }
};


I don't think it is possible to make a general matrix class consume less memory or run quicker than that. Compilers generate pretty good code for the accessor functions. You probably have to use a different data type such as a sparse matrix to improve performance much.