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");
source to share
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]; }
};
source to share
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".
source to share
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;
}
}
source to share