Accessing structure members in an array is similar: padding in structures other than arrays?

Suppose I have a structure that contains multiple members, each of which is of the same type. I would like to safely access the members by both name and array index, so I introduced union

which contains the actual struct

and the array that matches the members struct

in terms of number and type. See the following code, which illustrates how I would like to "view" in struct

two different ways, using this union

:

#include <iostream>

template<typename elemT>
struct productData {
    elemT color;
    elemT size;
    elemT application;
    elemT division;
};

template<typename elemT>
union productDataAsArray {
    struct productData<elemT> p;
    elemT arr[(sizeof(productData<elemT>) / sizeof(elemT))];
};

int main() {

    union productDataAsArray<int> myProduct;
    myProduct.p.color = 10;
    myProduct.p.size = 20;
    myProduct.p.application = 30;
    myProduct.p.division = 40;

    for (int i=0; i<4; i++) {
        std::cout << "elem #" << i << ":" << myProduct.arr[i] << std::endl;
    }
}

      

Is it safe / guaranteed by the standard to assume that the memory layout of the array matches the memory layout struct

, so that two different accessors achieve the desired results for any type elemT

?

The following static_asserts

with several different types indicates that it should work. But is it really guaranteed?

static_assert(sizeof(productData<int>)==sizeof(productDataAsArray<int>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char>)==sizeof(productDataAsArray<char>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<double>)==sizeof(productDataAsArray<double>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char*>)==sizeof(productDataAsArray<char*>::arr), "padding/alignment inconsistency");

class alignas(16) testClass {
    int x;
    int y;
    virtual void test() {};
};

static_assert(sizeof(productData<testClass>)==sizeof(productDataAsArray<testClass>::arr), "padding/alignment inconsistency");

      

+3


source to share


4 answers


Your UB way, you can instead do something like:



template<typename elemT>
class productData {
    elemT data[4];
public:
    const elemT& operator[](int i) const { return data[i];}
    const elemT& color() const { return data[0]; }
    const elemT& size() const { return data[1]; }
    const elemT& application() const { return data[2]; }
    const elemT& division() const { return data[3]; }

    elemT& operator[](int i) { return data[i];}
    elemT& color() { return data[0]; }
    elemT& size() { return data[1]; }
    elemT& application() { return data[2]; }
    elemT& division() { return data[3]; }
};

      

+5


source


No need to talk about memory layout here to start.

You are already calling UB by reading from a member union

other than the last assigned one. There is no exception to this rule in standard C ++.



So, to be clear, the answer to all of your "Is it safe / guaranteed" questions is "No".

+4


source


But is it really guaranteed?

Not.

You are calling Undefined Behavior by accessing an inactive element union

.

+2


source


As mentioned in other posts, this is not guaranteed.

Yours is static_assert

also insufficient for verification, since you could fill in different places. You should explicitly check the memory location in your static assertions:

namespace {
const productDataAsArray<int> testObj{0};
static_assert(&testObj.p.color == &testObj.arr[0]);
static_assert(&testObj.p.size == &testObj.arr[1]);
static_assert(&testObj.p.application == &testObj.arr[2]);
static_assert(&testObj.p.division == &testObj.arr[3]);
}

      

The best solution is indexing with an enum:

enum productDataIndexes {
  PRODUCT_COLOR = 0,
  PRODUCT_SIZE,
  PRODUCT_APPLICATION,
  PRODUCT_DIVISION,
  NUM_PRODUCT_INDEXES
};

int main() {
    ...
    myProduct[PRODUCT_COLOR] = 10;
    myProduct[PRODUCT_SIZE] = 20;
    myProduct[PRODUCT_APPLICATION] = 30;
    myProduct[PRODUCT_DIVISION] = 40;

    for (int i=0; i<NUM_PRODUCT_INDEXES; i++) {
        std::cout << "elem #" << i << ":" << myProduct[i] << std::endl;
    }
}

      

+1


source







All Articles