The c++ macro offsetof is just defined behaviour when used on standard layout types. As I understood this is because the compiler can change the memory layout of the data depending on the context of the code it runs. (For example when a variable is never used)
However, what I wondered is whether all elements stored in a range share the same layout. Or, in other words, if following code is well defined:
template<typename T>
concept has_member_int = requires(const T& x)
{
{ x.member } -> std::same_as<int>;
};
template <std::ranges::Range Range_t, has_member_int T>
void setEveryMemberTo20(Range_t<T> range)
{
if (range.size() > 0)
{
auto& firstElement = *(range.begin());
auto ptrdiffToMember = &(firstElement.member) - &firstElement;
for (auto& element : range)
{
*(reinterpret_cast<int*>(&element + ptrdiffToMember)) = 20;
}
}
}
Of course they do, otherwise traversing through multiple elements of the same type and accessing the same member of each element would be impossible. The offset of a given member is relative to the element's type. That offset is the same for all instances of that type. Thus, the combination of all the members within a type constitutes the type's layout, and that layout remains consistent across all uses of the type.
However, your handling of the member offset is all wrong. You are calculating the offset by subtracting a
T*pointer from anint*pointer, whichmemberwithinT.And then you are applying that offset to a
T*pointer, which will advance the pointer by that manyTinstances, not by that many bytes. IOW, if the offset ofmemberwithinTis 4, you are advancing theT*pointer bysizeof(T) * 4bytes, not by 4 bytes only.I think you need to brush up on how pointer arithmetic actually works.
Try something more like this instead:
But, as @alterigel stated in comments, just use
element.member = 20;instead. You don't need to deal with pointer manipulation at all: