Your code invokes undefined behavior because x.data() does not point to an array of pointers but to an array of 1000 objects of type char. You should be thankful that it crashes… ;-)
One way to access a contiguous buffer of some type as if it was a multidimensional array is to have another object that represents a multidimensional view into this buffer. This view object can then, e.g., provide member functions to access the data using a multidimensional index. To enable the a[i][j][k] kind of syntax (which you seem to be aiming for), provide an overloaded [] operator which returns a proxy object that itself offers an operator [] and so on until you get down to a single dimension.
For example, for the case that dimensions are fixed at compile time, we can define
template <int Extent, int... Extents>
struct row_major_layout;
template <int Extent>
struct row_major_layout<Extent>
{
template <typename T>
static auto view(T* data) { return data; }
};
template <int Extent, int... Extents>
struct row_major_layout
{
static constexpr int stride = (Extents * ... * 1);
template <typename T>
class span
{
T* data;
public:
span(T* data) : data(data) {}
auto operator[](std::size_t i) const
{
return row_major_layout<Extents...>::view(data + i * stride);
}
};
template <typename T>
static auto view(T* data) { return span<T>(data); }
};
and then simply create and access such a row_major_layout view
void test()
{
constexpr int M = 7, N = 2, K = 5;
std::vector<int> bla(row_major_layout<M, N, K>::size);
auto a3d = row_major_layout<M, N, K>::view(data(bla));
a3d[2][1][3] = 42;
}
live example here
Or in case the array bounds are dynamic:
template <int D>
class row_major_layout;
template <>
class row_major_layout<1>
{
public:
row_major_layout(std::size_t extent) {}
static constexpr std::size_t size(std::size_t extent)
{
return extent;
}
template <typename T>
friend auto view(T* data, const row_major_layout&)
{
return data;
}
};
template <int D>
class row_major_layout : row_major_layout<D - 1>
{
std::size_t stride;
public:
template <typename... Dim>
row_major_layout(std::size_t extent, Dim&&... extents)
: row_major_layout<D - 1>(std::forward<Dim>(extents)...), stride((extents * ... * 1))
{
}
template <typename... Dim>
static constexpr std::size_t size(std::size_t extent, Dim&&... extents)
{
return extent * row_major_layout<D - 1>::size(std::forward<Dim>(extents)...);
}
template <typename T>
class span
{
T* data;
std::size_t stride;
const row_major_layout<D - 1>& layout;
public:
span(T* data, std::size_t stride, const row_major_layout<D - 1>& layout)
: data(data), stride(stride), layout(layout)
{
}
auto operator[](std::size_t i) const
{
return view(data + i * stride, layout);
}
};
template <typename T>
friend auto view(T* data, const row_major_layout& layout)
{
return span<T>(data, layout.stride, layout);
}
};
and
void test(int M, int N, int K)
{
std::vector<int> bla(row_major_layout<3>::size(M, N, K));
auto a3d = view(data(bla), row_major_layout<3>(M, N, K));
a3d[2][1][3] = 42;
}
live example here