13

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).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • I read that several times, but still don't get what you want to do, do you want a generic *property* class that is aware of what owns it - if so, why would it need to know what owns it? I should imagine all a property really needs is ability to accept a value and return the value? – Nim Feb 08 '11 at 19:07
  • 1
    I don't get it either.. What are you trying to do? – mfontanini Feb 08 '11 at 19:13
  • @Nim: Yes, I want a generic "property" class that is aware of what owns it. For property it's necessary if the property value should be *computed*. In my case it is however a functor that needs to pass pointer to the owner to the underlying method. – Jan Hudec Feb 08 '11 at 19:47
  • 2
    Ha ha! I certainly haven't read your question (life is too short), but I've seen that you downvoted both responses to it. You need to rethink your knowledge-gathering strategy, Jan. – TonyK Feb 08 '11 at 20:01

4 Answers4

9

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.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • You should probably note that if someone inherits a polymorphic class from `foo` this solution will break. – Mark B May 15 '13 at 17:23
  • @MarkB: Will it? No matter how `foo` is derived from, the member is still at the same offset from the `foo` subinstance, so the `owner()` returns pointer to that subinstance, which is exactly where a `foo *` is expected to point. – Jan Hudec May 16 '13 at 06:48
  • 1
    @JanHudec, that's brilliant, event half a decade later. Thanks. – Kumputer Jun 23 '16 at 00:47
2

1) There's a gcc extension which seemed fitting:

enum{ d_y = __builtin_choose_expr(N,offsetof(X,y),0) };

But it didn't work as expected, even though manual says
"the built-in function does not evaluate the expression that was not chosen"

2) member pointers seemed interesting, eg. offsetof can be defined like this:

template< class C, class T >
int f( T C::*q ) {
  return (int)&((*(C*)0).*q);
}

But I still didn't find a way to turn this into constexpr.

3) For now, here's another version:

#include <stdio.h>

#pragma pack(1)

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

#define STRUCT( A ) template< int N=0 > struct A {
#define CHILD( A, N, B, y ) }; template<> struct A<N> : A<N-1> \
  { B<A<N>,sizeof(A<N-1>)> y;
#define STREND };

STRUCT( A )
  int x0;
  int x1;
  CHILD( A,1, B, y );
  short x2;
  CHILD( A,2, B, z );
  char x3;
STREND

typedef A<2> A1;

int main( void ) {
  A1 a;
  a.y.f();
  a.z.f();
}
Shelwien
  • 2,160
  • 15
  • 17
  • The expression that is not chosen is not relevant. The member always exists and the offsetof branch has to be chosen. It can't be evaluated in time--using an (inline) function is necessary. – Jan Hudec Feb 09 '11 at 07:23
  • There's still no proof that a pure compile-time way doesn't exist (in fact i already posted 2). – Shelwien Feb 09 '11 at 07:33
1

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();
}
Shelwien
  • 2,160
  • 15
  • 17
  • The __if_exists/__if_not_exists should not be needed. It is an error if it does not exist anyway. And that's the only MS-specific thing in there, right? – Jan Hudec Feb 08 '11 at 21:04
  • I did a little tweaking (basically replacing `offs` with `offsetof` from ``, but gcc is refusing to compile it because it does not allow reference to members before they are declared (except in method bodies). In fact visual C++ has to cut some corners, because the required alignment might in general depend on the template argument. – Jan Hudec Feb 08 '11 at 21:17
  • The idea's that the class is defined first time without offsets, then defined again using offsets from the first instance. Its easy to do with eg. 2x #include + macro redefinition, but that's inconvenient. However, I still don't know how to setup SFINAE for nonexistant class members (redefining operator-> or ->* seemed promising, but didn't work). So it won't work without __if_exists. – Shelwien Feb 08 '11 at 23:12
  • I only define instances of template. And as to the fact that offset is taken from the different instance, it should normally still work as expected - imho its not any less universal than the whole idea to use offsetof() to find parent's base. – Shelwien Feb 09 '11 at 11:49
  • @Shelwien: Ah, yes, I see. That's indeed a clever hack. – Jan Hudec Feb 10 '11 at 06:04
0

Assuming the calls actually need a reference to the containing object, just store the reference to the owner. Unless you have specific memory profiling evidence that it's causing a significant memory increase to store the extra references, just do it the obvious way.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 1
    Have you read the question? That's exactly what the middle option does and than I asked whether there is a way to get without that reference. – Jan Hudec Feb 08 '11 at 19:44
  • 1
    @Jan Hudec Why do you think the extra references are a real problem that needs to be fixed? My first reading of the question is that there may not be an actual problem here. – Mark B Feb 08 '11 at 20:05
  • 2
    Well, the question specifically asks for solution that does not have them. It's not a matter whether there is a problem with them or not—I specifically excluded them because otherwise the matter is trivial and not worth being discussed. – Jan Hudec Feb 08 '11 at 20:59