Search code examples
c++arrayscastingundefined-behavior

Using a std::vector<std::array<T,N>> as a flat contiguous array of T


A std::vector stores its elements contiguously in memory. An std::array<T,N> is just a wrapper around N contiguous T elements, stored directly in the object itself.

Hence I am wondering if a std::vector<std::array<T,N>> of size n can also be seen as an array of T of size N*n.

Consider the following code (also here):

int main() {
    // init a vector of 10 arrays of 3 elements each
    std::vector<std::array<int,3>> v(10);

    // fill it
    for (int i=0; i<10; ++i) {
        v[i] = {3*i,3*i+1,3*i+2};
    }

    // take the first array
    std::array<int,3>* a_ptr = v.data();

    // take the first element of the array
    int* i_ptr = a_ptr->data();

    // dereference the pointer for 3*10 elements
    for (int i=0; i<30; ++i) {
        std::cout << i_ptr[i] << ',';
    }
    return 0;
}

It appears to work for my setup, but is the code legit or is it undefined behavior?

If I were to replace std::array<int,3> with a struct:

    struct S { 
        int i,j,k;
    };

    std::vector<S> v(10);
    S* s_ptr = v.data();
    int* i_ptr = &S.i;

Would it work the same?

What would be the alternatives that would not trigger undefined behavior?

Context: I have a std::vector<std::array<int,3>> and I want to serialize it to send it over a network.


Solution

  • No, but yes.

    No, the C++ standard does not let you treat structs with arrays in them, or even structs with uniform elements, packed together as a single contiguous larger array of the base type.

    Yes, in that enough in world production code requires this to work that no compiler is going to break it from working any time soon.

    Things to watch out for include packing. Add static asserts that the size of the structs and arrays is what you expect. Also, avoid being fancy with object lifetime or going "backwards" from an index besides the first one; the reachability rules of C++ are slightly more likrly to bite you if you do strange things.

    Another concern is that these constructs are under somewhat active investigation; the fiasco that std vector cannot be implemented in standard C++, for example, or std::launder and bit_cast. When a real standard way to do what you want develops, switching to it might be a good idea, because the old technique will become less likely to be supported.