3

I have two classes, roughly defined like this:

class Inner {
public:
  bool is_first;
};

class Outer {
public:
  char some_other_member;
  Inner first;
  Inner second;
}

I known that Inners only ever live inside Outers, and that the respective bool flag will be set to true if and only if the respective object is the first member, not the second.

I am looking for a standard-compliant way of deriving a pointer to the Outer object containing some Inner object. Of course I could just store a pointer inside every Inner object, but since the Inner class is very small and I have lots of them, that seems like a waste of memory (and thus precious cache).

Obviously, the compiler should know the memory offset between first, second and the containing Outer object. The question is: Is there a standard-compliant way of telling the compiler "get that offset, subtract it from the pointer to Inner and make it an Outer pointer"?

I know I could use casting to void if Outer would contain the Inners as base subobjects (e.g. this) - I very much feel like something similar should be possible for member subobjects?

Passer By
  • 19,325
  • 6
  • 49
  • 96
Lukas Barth
  • 2,734
  • 18
  • 43
  • 1
    If only `first` *or* `second` will be valid at the same time, why have the two members at all? What is the *real* and *actual* problem that lead to a design like this? Can't you solve it through inheritance and polymorphism? – Some programmer dude May 28 '18 at 12:53
  • No, both will be valid at the same time. The two inner objects actually are two red-black tree nodes representing the start and end of an interval (which is the outer class). The thing is: They *know* whether they are `first` or `second`. – Lukas Barth May 28 '18 at 12:58
  • 1
    I would like you to advice that you should start c++ programming. It is bad practice to do pointer arithmetic's like you plan. You did not calculate on pointers, you also change types. From OOP perspective you put all the knowledge from your class to "something" outside. Why not simply return a pointer from the instance which points to the member? This will keep the type and keeps all informations inside. BTW: Why you need an external! pointer to inside the object. All that is bad design! – Klaus May 28 '18 at 13:07
  • Hi Klaus. Thanks for the advice - I assure you, I usually do "clean" OOP. However, when it comes to basic data structures, I sometimes need that couple of extra percents of efficiency that comes with saving a pointer. As I said, I'm talking about the nodes of a Red-Black-Tree here. – Lukas Barth May 28 '18 at 13:15
  • 1
    @LukasBarth: I believe you are at the wrong path! If you believe, that manual subtraction of pointers will do it smarter then returning a pointer to a subobject, than you are simply wrong. If you return a pointer to a subobject, the compiler will simply create a method which adds the known constant offset to the this pointer. Exactly what you need. So I can't understand what your benefit could be. Try first to code it in c++ with OOP design. If you run into performance problems, go the generated assembly. If you believe, your handcrafted code can be better, you have the option to optimize. – Klaus May 28 '18 at 13:31
  • @Klaus So where should I add that method to "return a pointer to the subobject"? In `Outer`? The situation is: I don't have a pointer to an `Outer`. I have a pointer to an `Inner` and *I'm looking for* a pointer to the respective `Outer`. The problem is not getting the subobject from the containing object; that's trivial. It's the other way round. – Lukas Barth May 28 '18 at 13:34
  • 1
    @LukasBarth: The problem starts at the general design: Why you need a flag for something which is stored in an also known element. The knowledge that it is stored as first or second element is already available. The next is, that you have an object which can only be alive inside another one but you want to deal with pointers to the inner ones. All that feels for me that there is some inversion of logic. Maybe it is a idea to think again of the underlying problem? – Klaus May 28 '18 at 14:22
  • There is no standards compliant way. Perhaps you want to rethink your design – n. m. could be an AI May 28 '18 at 16:12

3 Answers3

4

You should note that the problem of obtaining the pointer to a parent object from a pointer to a member is generally not solvable.

The offsetof and casting only works for standard layout types. In the other cases it is undefined behavior.

It fails for instance for multiple virtual inheritance. In that case member access is implemented through a more complicated way than adding a compile time offset. Perhaps this is practically not so relevant as it is very rarely used, but you explicitly asked for standard compliance.

Andreas H.
  • 5,557
  • 23
  • 32
  • Thanks, I was afraid of that. Unfortunately, my `Outer` does indeed use inheritance, and has non-static data members in multiple classes in its inheritance hierarchy. I still have the feeling like it shouldn't be a problem for the compiler to compute the offset in such a case - but I don't want to go into UB-land. – Lukas Barth May 28 '18 at 13:36
  • You could do a static assert in your class or `get_outer` function that checks for standard-layout-ness, e.g. `static_assert( std::is_standard_layout::value, "Outer must be standard layout");` Then you are safe, and stay far away from UB-land :-) (`offsetof` and casting is guaranteed to work with standard layout types). Practically `offsetof` also works for many non-standard layout types, but this is UB. – Andreas H. May 28 '18 at 14:43
  • 1
    From C++17 it is “conditionally defined” for other than standard layout types. It does not seem to say on what condition it is defined though. – Jan Hudec May 29 '18 at 13:11
3

That's what offsetof is for.

Something like:

Outer *Inner::getOuter() {
    size_t offset = flag ? offsetof(Outer, first) : offsetof(Outer, second);
    return reinterpret_cast<Outer *>(
        reinterpret_cast<char *>(this) - offset);
}

Note that you are reinterpret_casting, so you are fully responsible to know what you are doing. If the flags get out of sync, you'll get undefined behaviour.

Note that pointer arithmetic is only allowed within an array, but the §6.9/4 ([basic.types]) explicitly says every object is stored in such an array of unsigned char:

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).

and within this array, the pointer arithmetic is defined. But you need to be sure you are indeed within the allocation.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • This does have the problem that Passer By mentions in his answer, right? Essentially: Subtracting from a `char *` expression is only then strictly defined behavior if that expression really points to a `char` array? *Edit*: Link to the standard: http://eel.is/c++draft/expr.add#4 – Lukas Barth May 28 '18 at 13:11
  • 1
    @LukasBarth, no, `char *` is explicitly permitted to alias anything, so this *is defined* as long as you stay within the allocation. – Jan Hudec May 28 '18 at 17:05
  • @LukasBarth, in fact it is the Passer By's answer that is *not* correct (as evidenced by a very real and once very common architecture where depending on compiler settings pointer addition was more complicated than integer addition). – Jan Hudec May 28 '18 at 19:14
3

No solution is strictly correct, you run afoul some pointer arithmetic rules. However, there is an implementation-defined solution, which almost always does what you expect it to

auto get_outer(Inner& i)
{
    return (Inner*)(((uintptr_t)&i) - offsetof(Outer, first));
}

The catch being pointer and integral type conversions are implementation defined.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/172010/discussion-on-answer-by-passer-by-c-get-adress-of-complete-object-containing). –  May 29 '18 at 14:15