what I wondered is whether all elements stored in a range share the same layout
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 an int*
pointer, which
- should not even compile, as you can't subtract pointers of different types.
- even if it did compile, that would not give you the correct byte offset of
member
within T
.
And then you are applying that offset to a T*
pointer, which will advance the pointer by that many T
instances, not by that many bytes. IOW, if the offset of member
within T
is 4, you are advancing the T*
pointer by sizeof(T) * 4
bytes, not by 4 bytes only.
I think you need to brush up on how pointer arithmetic actually works.
Try something more like this instead:
auto& firstElement = *(range.begin());
// or: T& firstElement = ...
auto ptrdiffToMember = reinterpret_cast<uintptr_t>(&firstElement.member) - reinterpret_cast<uintptr_t>(&firstElement);
// or: auto ptrdiffToMember = offsetof(T, member);
for (auto& element : range)
{
*(reinterpret_cast<int*>(reinterpret_cast<uintptr_t>(&element) + ptrdiffToMember)) = 20;
}
But, as @alterigel stated in comments, just use element.member = 20;
instead. You don't need to deal with pointer manipulation at all:
template <std::ranges::Range Range_t, has_member_int T>
void setEveryMemberTo20(Range_t<T> range)
{
for (auto& element : range)
{
element.member = 20;
}
}