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;
}
}
}
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
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;
}
}