Solution for object let's the user choose the number of dimensions. A little robust, my C++ maybe is not the best, but it was fun implementing. nvector<T> represents resizable (in dimensions and count of elements in each dimension) array of element of T type, although only some resize functions are implemented. narray<T> is the same, but the number of dimensions is not resizable. This works around the idea of recalculating index position of a multidimensional array using a single continuous array.
#include <cstdio>
#include <vector>
#include <iostream>
#include <cstddef>
#include <cstdarg>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <memory>
#include <cstring>
using namespace std;
template<typename T>
class narray {
public:
    static size_t compute_size(initializer_list<size_t>& dims) {
        return accumulate(dims.begin(), dims.end(), 1, multiplies<size_t>());
    }
    static size_t compute_size(vector<size_t>& dims) {
        return accumulate(dims.begin(), dims.end(), 1, multiplies<size_t>());
    }
    static size_t compute_distance(vector<size_t>& dims) {
        return dims.size() > 1 ? dims[1] : 1;
    }
    static vector<size_t> remove_one_dim(vector<size_t> dims_) {
        return vector<size_t>(dims_.begin() + 1, dims_.end());
    }
    narray(initializer_list<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    narray(vector<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    T operator*() {
        return *data_;
    }
    T* operator&() {
        return data_;
    }
    void operator=(T v) {
        if (dims_.size() != 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        *data_ = v;
    }
    void operator=(initializer_list<T> v) {
        if (v.size() > size())
            throw runtime_error(__PRETTY_FUNCTION__);
        copy(v.begin(), v.end(), data_);
    }
    T* data() {
        return data_;
    }
    T* data_last() {
        return &data()[compute_size(dims_)];
    }
    size_t size() {
        return compute_size(dims_);
    }
    size_t size(size_t idx) {
        return dims_[idx];
    }
    narray<T> operator[](size_t idx) {
        if (dims_.size() == 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        return narray<T>(remove_one_dim(dims_),
                &data_[idx * compute_distance(dims_)]);
    }
    class iterator {
    public:
        iterator(initializer_list<size_t>& dims, T* data) :
            dims_(dims), data_(data) { }
        iterator(vector<size_t>& dims, T* data) :
            dims_(dims), data_(data) { }
        iterator operator++() {
            iterator i = *this;
            data_ += compute_distance(dims_);
            return i;
        }
        narray<T> operator*() {
            return narray<T>(remove_one_dim(dims_), data_);
        }
        bool operator!=(const iterator& rhs) {
            if (dims_ != rhs.dims_)
                throw runtime_error(__PRETTY_FUNCTION__);
            return data_ != rhs.data_;
        }
    private:
        vector<size_t> dims_;
        T* data_;
    };
    iterator begin() {
        return iterator(dims_, data());
    }
    iterator end() {
        return iterator(dims_, data_last());
    }
private:
    vector<size_t> dims_;
    T* data_;
};
template<typename T>
class nvector {
public:
    nvector(initializer_list<size_t> dims) :
        dims_(dims), data_(narray<T>::compute_size(dims)) {}
    nvector(vector<size_t> dims) :
        dims_(dims), data_(narray<T>::compute_size(dims)) {}
    nvector(initializer_list<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    nvector(vector<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    T* data() {
        return data_.data();
    }
    T* data_last() {
        return &data()[narray<T>::compute_size(dims_)];
    }
    size_t size() {
        return narray<T>::compute_size(dims_);
    }
    narray<T> operator&() {
        return narray<T>(dims_, data());
    }
    narray<T> operator[](size_t idx) {
        if (dims_.size() == 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        return narray<T>(narray<T>::remove_one_dim(dims_),
                &data()[idx * narray<T>::compute_distance(dims_)]);
    }
    void operator=(initializer_list<T> v) {
        if (v.size() > size())
            throw runtime_error(__PRETTY_FUNCTION__);
        copy(v.begin(), v.end(), data_.begin());
    }
    auto begin() {
        return typename narray<T>::iterator(dims_, data());
    }
    auto end() {
        return typename narray<T>::iterator(dims_, data_last());
    }
    // add and remove dimensions
    void dimension_push_back(size_t dimsize) {
        dims_.push_back(dimsize);
        data_.resize(size());
    }
    void dimension_pop_back() {
        dims_.pop_back();
        data_.resize(size());
    }
    // TODO: resize dimension of index idx?
private:
    vector<size_t> dims_;
    vector<T> data_;
};
int main()
{
    nvector<int> A({2, 3});
    A = { 1,2,3, 4,5,6 };
    assert(A.size() == 6);
    assert(&A[0] == &A.data()[0]);
    assert(&A[0][0] == &A.data()[0]);
    assert(&A[1] == &A.data()[3]);
    assert(&A[0][1] == &A.data()[1]);
    assert(&A[1][1] == &A.data()[4]);
    cout << "Currently array has " << A.size() << " elements: " << endl;
    for(narray<int> arr1 : A) { // we iterate over arrays/dimensions
        for(narray<int> arr2 : arr1) { // the last array has no dimensions
            cout << "elem: " << *arr2 << endl;
        }
    }
    cout << endl;
    // assigment example
    cout << "Now it is 4:  " << *A[1][0] << endl;
    A[1][0] = 10;
    cout << "Now it is 10: " << *A[1][0] << endl;
    return 0;
}
This code needs still much more work. It works only as a simple example. Maybe use shared_ptr in narray? Implement better exceptions?
So creating an array of n=5 dimensions with sizes size1, size2, size3, size4 and size5 would like this:
 narray<int> arr({size1, size2, size3, size4, size5});
 arr[0][1][2][3][4] = 5; // yay!