Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ class member variable knowing its own offset

Is it possible to have a member variable, that would be able to calculate pointer to the containing object from pointer to itself (in it's method)?

Let's have a foreign call interface wrapped in API like this:

template <typename Class, MethodId Id, typename Signature>
class MethodProxy;

template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodProxy<Class, Id, ReturnT ()(Arg1T) {
  public:
    ReturnT operator()(Class &invocant, Arg1T arg1);
};

and similarly for other numbers of arguments from 0 to N. For each class on the foreign side, one C++ class is declared with some traits and this template uses those traits (and more traits for argument types) to find and invoke the foreign method. This can be used like:

Foo foo;
MethodProxy<Foo, barId, void ()(int)> bar;
bar(foo, 5);

Now what I would like to do is define Foo in such way, that I can call like:

Foo foo;
foo.bar(5);

without repeating the signature multiple times. (obviously creating a static member and wrapping the call in a method is simple, right). Well, in fact, that's still easy:

template <typename Class, MethodId Id, typename Signature>
class MethodMember;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodMember<Class, Id, ReturnT ()(Arg1T) {
    MethodProxy<Class, Id, Signature> method;
    Class &owner;
  public:
    MethodMember(Class &owner) : owner(owner) {}
    ReturnT operator()(Arg1T arg1) { return method(owner, arg1); }
};

That however means the object will end up containing many copies of pointer to itself. So I am looking for a way to make these instances being able to calculate the owner pointer from this and some additional template arguments.

I was thinking along the lines of

template <typename Class, size_t Offset, ...>
class Member {
    Class *owner() {
        return reinterpret_cast<Class *>(
            reinterpret_cast<char *>(this) - Offset);
    }
    ...
};
class Foo {
    Member<Foo, offsetof(Foo, member), ...> member;
    ...
};

but this complains that Foo is incomplete type at the point.

Yes, I know offsetof is supposed to only work for "POD" types, but in practice for any non-virtual member, which this will be, works. I have similarly tried to pass pointer-to-(that)-member (using dummy base-class) in that argument, but that does not work either.

Note, that if this worked, it could also be used to implement C#-like properties delegating to methods of the containing class.

I know how to do the wrapper methods mentioned above with boost.preprocessor, but the argument lists would have to be specified in a weird form. I know how to write macro to generate generic wrappers via templates, but that would probably give poor diagnostics. It would also be trivial if the calls could look like foo.bar()(5). But I'd like to know whether some clever trick would be possible (plus only such clever trick would probably be usable for properties too).

Note: The member type can't be actually specialized on either member pointer to it nor it's offset, because the type must be known before that offset can be assigned. That's because the type can affect required alignment (consider explicit/parcial specialization).

like image 703
Jan Hudec Avatar asked Feb 08 '11 18:02

Jan Hudec


2 Answers

Asking a question is the best way to realize the answer, so this is where I've got:

The offset can't be a template argument, because the type has to be known before the offset can be calculated. So it has to be returned by a function of the argument. Let's add a tag type (dummy struct) and either a put an overloaded function into Owner or directly into the tag. That way we can define everything we need on one place (using a macro). The following code compiles fine with gcc 4.4.5 and prints correct pointer for all members:

#include <cstddef>
#include <iostream>

using namespace std;

(just preamble to make it really compile)

template <typename Owner, typename Tag>
struct offset_aware
{
    Owner *owner()
    {
        return reinterpret_cast<Owner *>(
            reinterpret_cast<char *>(this) - Tag::offset());
    }
};

This is what's needed to make the object aware of it's own offset. Property or functor or some other code can be added freely to make it useful. Now we need to declare some extra stuff along with the member itself, so let's define this macro:

#define OFFSET_AWARE(Owner, name) \
    struct name ## _tag { \
        static ptrdiff_t offset() { \
            return offsetof(Owner, name); \
        } \
    }; \
    offset_aware<Owner, name ## _tag> name

This defines structure as the tag and puts in a function returning the required offset. Than it defines the data member itself.

Note, that the member needs to be public as defined here, but we could easily add a 'friend' declaration for the tag support protected and private properties. Now let's use it.

struct foo
{
    int x;
    OFFSET_AWARE(foo, a);
    OFFSET_AWARE(foo, b);
    OFFSET_AWARE(foo, c);
    int y;
};

Simple, isn't it?

int main()
{
    foo f;

    cout << "foo f = " << &f << endl
        << "f.a: owner = " << f.a.owner() << endl
        << "f.b: owner = " << f.b.owner() << endl
        << "f.c: owner = " << f.c.owner() << endl;
    return 0;
}

This prints the same pointer value on all lines. C++ standard does not allow members to have 0 size, but they will only have the size of their actual content or 1 byte if they are otherwise empty compared to 4 or 8 (depending on platform) bytes for a pointer.

like image 62
Jan Hudec Avatar answered Nov 13 '22 02:11

Jan Hudec


For now, here's one MS-specific solution, still thinking how to make it more general

#include <stdio.h>

#define offs(s,m)   (size_t)&(((s *)0)->m)
#define Child(A,B,y) \
  __if_exists(X::y) { enum{ d_##y=offs(X,y) }; } \
  __if_not_exists(X::y) { enum{ d_##y=0 }; } \
  B<A,d_##y> y;

template <class A, int x>
struct B {
  int z;
  void f( void ) {
    printf( "x=%i\n", x );
  }
};

template< class X >
struct A {
  int x0;
  int x1;
  Child(A,B,y);
  Child(A,B,z);
};

typedef A<int> A0;

typedef A<A0> A1;

int main( void ) {
  A1 a;
  a.y.f();
  a.z.f();
}
like image 2
Shelwien Avatar answered Nov 13 '22 01:11

Shelwien