简体   繁体   中英

is the pointerdiff between members of different objects in containers the same?

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM