3

(This is not a duplicate of this or this that refer to fixed sizes, the issue is not to understand how pointers are stored, but if the compiler can automate the manual function).

Based on this SO question multidimensional arrays are stored sequentially.

// These arrays are the same
int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}}; 
int array2[6] = { 0, 1, 2, 3, 4, 5 }; 

However I'm trying to create a 2 dimension array of floats in pre-allocated memory:

float a[5][10] 
float b[50]; // should be same memory

Then I'm trying:

vector<char> x(1000);
float** a = (float**)x.data();
a[0][1] = 5;

The above code crashes, obviously because the compiler does not know the size of the array to allocate it in memory like in the compiler-level known array in the first example.

Is there a way to tell the compiler to allocate a multi dimensional array in sequential memory without manually calculating the pointers (say, by manually shifting the index and calling placement new for example)?

Currently, I'm doing it manually, for example:

template <typename T> size_t CreateBuffersInMemory(char* p,int n,int BufferSize)
{
    // ib = T** to store the data
    int ty = sizeof(T);

    int ReqArraysBytes = n * sizeof(void*);
    int ReqT = ReqArraysBytes * (ty*BufferSize);
    if (!p)
        return ReqT;

    memset(p, 0, ReqT);
    ib = (T**)p;
    p += n * sizeof(void*);
    for (int i = 0; i < n; i++)
    {
        ib[i] = (T*)p;
        p += ty*BufferSize;
    }
    return ReqT;
}

Thanks a lot.

Michael Chourdakis
  • 8,819
  • 2
  • 32
  • 61
  • Yes OK, but can I have the compiler initialize the array automatically as it does with static arrays? – Michael Chourdakis Apr 09 '19 at 13:40
  • 4
    The syntax of accessing `float**` is the same: `a[i][j]` but the semantics of accessing a true multidimensional array is entirely different. `float**` is a pointer to a pointer. So the first dimension should contain pointers to the rows. – rustyx Apr 09 '19 at 13:46
  • 1
    Possible duplicate of [How do I declare a 2d array in C++ using new?](https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new) – rustyx Apr 09 '19 at 13:48
  • 2
    @rustyx: I don't think it's exactly a duplicate, but understanding how `float**` vs. `float [][]` works might help in answering the question. Maybe you could simply refer to the post? – andreee Apr 09 '19 at 13:51
  • Possible duplicate of [How to cast simple pointer to a multidimensional-array of fixed size?](https://stackoverflow.com/questions/11869056/how-to-cast-simple-pointer-to-a-multidimensional-array-of-fixed-size) – drescherjm Apr 09 '19 at 14:41
  • ***but if the compiler can automate the manual function*** The answer is it can't outside of what https://stackoverflow.com/a/55594599/487892 does. – drescherjm Apr 09 '19 at 18:45

4 Answers4

3

To allocate T[rows][cols] array as a one-dimensional array allocate T[rows * cols].

To access element [i][j] of that one-dimensional array you can do p[i * cols + j].

Example:

template<class T>
struct Array2d {
    T* elements_;
    unsigned columns_;

    Array2d(unsigned rows, unsigned columns)
        : elements_(new T[rows * columns]{}) // Allocate and value-initialize.
        , columns_(columns)
    {}

    T* operator[](unsigned row) {
        return elements_ + row * columns_;
    }

    // TODO: Implement the special member functions.
};

int main() {
    Array2d<int> a(5, 10);
    a[3][1] = 0;
}
Maxim Egorushkin
  • 119,842
  • 14
  • 147
  • 239
2

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

Michael Kenzel
  • 14,512
  • 1
  • 25
  • 36
1

Based on this answer assuming you want an array of char you can do something like

std::vector<char> x(1000);
char (&ar)[200][5] = *reinterpret_cast<char (*)[200][5]>(x.data());

Then you can use ar as a normal two-dimensional array, like

char c = ar[2][3];
francesco
  • 4,122
  • 7
  • 15
  • 31
  • 1
    One thing about this approach is the 2D array must be a fixed size at compile time since you need to cast it to a fixed type. This will not be as flexible as a matrix class. – drescherjm Apr 09 '19 at 16:52
  • I think the above code has undefined behavior. A pointer to an array and a pointer to its first element are not [pointer interconvertible](http://eel.is/c++draft/basic.types#basic.compound-4). Furthermore, at least as soon as the element type of the array that `ar` refers to would be anything other than `char`, `unsigned char`, or `std::byte`, I would also believe the [strict aliasing rule](http://eel.is/c++draft/basic.lval#11) violated here. I don't think anyone should be doing this… – Michael Kenzel Apr 09 '19 at 17:07
0

For anyone trying to achieve the same, I 've created a variadit template function that would create a n-dimension array in existing memory:

template <typename T = char> size_t CreateArrayAtMemory(void*, size_t bs)
{
    return bs*sizeof(T);
}

template <typename T = char,typename ... Args>
size_t CreateArrayAtMemory(void* p, size_t bs, Args ... args)
{
    size_t R = 0;
    size_t PS = sizeof(void*);
    char* P = (char*)p;
    char* P0 = (char*)p;

    size_t BytesForAllPointers = bs*PS;
    R = BytesForAllPointers;

    char* pos = P0 + BytesForAllPointers;
    for (size_t i = 0; i < bs; i++)
    {
        char** pp = (char**)P;
        if (p)
            *pp = pos;
        size_t RLD = CreateArrayAtMemory<T>(p ? pos : nullptr, args ...);
        P += PS;
        R += RLD;
        pos += RLD;
    }
    return R;
}

Usage:

Create a 2x3x4 char array:

int j = 0;
size_t n3 = CreateArrayAtMemory<char>(nullptr,2,3,4);
std::vector<char> a3(n3);
char*** f3 = (char***)a3.data();
CreateArrayAtMemory<char>(f3,2,3,4);
for (int i1 = 0; i1 < 2; i1++)
{
    for (int i2 = 0; i2 < 3; i2++)
    {
        for (int i3 = 0; i3 < 4; i3++)
        {
            f3[i1][i2][i3] = j++;
        }
    }
}
Michael Chourdakis
  • 8,819
  • 2
  • 32
  • 61