Don't dynamically allocate in two dimensions like that. It's poison for your cache, and completely pointless. I see it all the time and I wish I didn't! Make yourself a nice std::vector<double> of size rows*cols instead.
Anyway, the trick to permit [width][height] is a proxy class. Have your operator[] return an instance of a class that has its own operator[] to do the second-level lookup.
Something like this:
#include <iostream>
#include <vector>
struct Matrix
{
    Matrix(const size_t columns, const size_t rows)
       : columns(columns)
       , rows(rows)
       , data(columns*rows, 0)
    {}
    size_t index(const size_t x, const size_t y) const
    {
        return x + y*columns;
    }
    double& at(const size_t x, const size_t y)
    {
        return data[index(x, y)];
    }
    double at(const size_t x, const size_t y) const
    {
        return data[index(x, y)];
    }
    template <bool Const>
    struct LookupHelper
    {
        using ParentType = std::conditional_t<Const, const Matrix, Matrix>;
        using ReturnType = std::conditional_t<Const, double, double&>;
        LookupHelper(ParentType& parent, const size_t x) : parent(parent), x(x) {}
        ReturnType operator[](const size_t y)
        {
            return parent.data[parent.index(x, y)];
        }
        const ReturnType operator[](const size_t y) const
        {
            return parent.data[parent.index(x, y)];
        }
    private:
        ParentType& parent;
        const size_t x;
    };
    LookupHelper<false> operator[](const size_t x)
    {
        return {*this, x};
    }
    LookupHelper<true> operator[](const size_t x) const
    {
        return {*this, x};
    }
private:
    const size_t columns, rows;
    std::vector<double> data;
};
int main()
{
    Matrix m(42, 3);
    m[15][3] = 1;
    std::cout << m[15][3] << '\n';
}
(In reality, you'd want to make it moveable and it could doubtlessly be tidied up a bit.)
Certainly, switching to operator() or a .at(width, height) member function is a lot easier…