2

I have a struct (Member) that can only be used as data member in some other struct (Container). By convention the name of the member is always m. Is there a reliable way for the member to obtain the address of the containing struct?

template<typename Struct>
struct Member;
{
  const Struct& s = ??;
                    // this - &Struct::m
};

struct Container
{
   Member<Container> m;
};

I was hoping that maybe using the pointer to member &Container::m might help to calculate back from the address of the Member object itself?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Rumburak
  • 3,416
  • 16
  • 27

2 Answers2

5

No, you cannot do this. You can determine the offset of m within Container and do pointer arithmetic to guess at the resulting address of the Container, but this will be:

  • unreliable
  • prone to catastrophic errors
  • UB (and therefore can cause symptoms including time travel — no, seriously!)

It might work consistently on a few platforms if you have all optimisations disabled but, really, please, just don't.

Either pass a pointer/reference to Container into Member<Container>'s constructor (after adding one), or further rethink your design. Why does the member need to be aware of the object encapsulating it? That is almost always wrong (though there are some passable use cases).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Passing a reference to Container into the Member is what I currently (plan to) do. I am thinking about ways to avoid a variadic form of CRTP by turning functions in the CRTP base classes into callable data members. – Rumburak Feb 26 '15 at 13:51
  • 1
    Can you elaborate on why this is undefined behavior? – Pedro LM Sep 02 '19 at 06:06
1

Not sure about "reliable", and it's the sort of hack that shouldn't be encouraged, but the following should give you a good starting point:

#include <cassert>
#include <cstddef>
#include <type_traits>

template<typename Struct>
struct Member
{
    Struct const* s = (Struct*)&((char*)this)[-(int)offsetof(Struct, m)];
};

struct Container
{
    int abc;
    int def;

    Member<Container> m;
};



int main(int argc, char* argv[])
{
    assert(std::is_standard_layout<Container>::value);

    Container c;

    Container const *p1 = &c;
    Container const *p2 = c.m.s;

    bool test = p1 == p2;

    return 0;
}

I added some members so that m has an actual non zero offset for testing, but it also works for zero offset.

Alexander Rautenberg
  • 2,205
  • 2
  • 22
  • 20