Search code examples
c++c++11operator-overloadingproxy-pattern

Overload -> operator to forward member-access through Proxy


I'm trying to wrap a Python PyObject* in an Object class. In Python, everything is a PyObject*. A list is a PyObject*, and each item in the list is itself a PyObject*. Which could even be another list. etc.

I'm trying to allow fooList[42] = barObj style syntax by means of a Proxy pattern (here).

Now that I have that working, I want to extend it so that fooList[42] can be used as an Object. Specifically I want to be able to handle...

fooList[42].myObjMethod()
fooList[42].myObjMember = ...

Object has a lot of methods, and currently fooList[42].myObjMethod() is going to first resolve fooList[42] into a Proxy instance, say tmpProxy, and then attempt tmpProxy.myObjMethod().

This means I would have to do

void Proxy::myObjMethod(){ return wrapped_ob.myObjMethod(); }

i.e. manually relay each of Object's methods through Proxy, which is ugly.

I can't see any perfect solution (see the above linked answer), but I would be happy to use:

fooList[42]->myObjMethod()

... as a compromise, seeing as -> can be overloaded (as opposed to . which cannot).

However, I can't find any documentation for overloading operator->.

My best guess is that it must return a pointer to some object (say pObj), and C++ will invoke pObj->whatever.


Below is my attempted implementation. However, I'm running into a 'taking the address of a temporary object of type Object' warning.

I have, within my Object class:

const Object operator[] (const Object& key)     const { 
    return Object{ PyObject_GetItem( p, key.p ) }; 
}

NOTE that 'const Object&' runs into 'taking the address of a temporary object of type Object' warning.

class Proxy {
private:
    const Object& container;
    const Object& key;

public:
    // at this moment we don't know whether it is 'c[k] = x' or 'x = c[k]'
    Proxy( const Object& c, const Object& k ) : container{c}, key{k}
    { }

    // Rvalue
    // e.g. cout << myList[5] hits 'const Object operator[]'
    operator Object() const {
        return container[key];
    }

    // Lvalue
    // e.g. (something = ) myList[5] = foo
    const Proxy&  operator= (const Object& rhs_ob) {
        PyObject_SetItem( container.p, key.p, rhs_ob.p );
        return *this; // allow daisy-chaining a = b = c etc, that's why we return const Object&
    }

    const Object* operator->() const { return &container[key]; }
    // ^ ERROR: taking the address of a temporary object of type Object
};

The idea is to allow myList[5]->someMemberObj = ... style syntax.

myList[5] resolves as a Proxy instance, which is wrapping an Object (the sixth element of myList). Let's call it myItem.

Now I want someProxy->fooFunc() or someProxy->fooProperty to invoke myItem.fooFunc() or myItem.fooProperty respectively.

I'm running into a 'taking the address of a temporary object of type Object' warning.


Solution

  • If you can change Object, you may add

    class Object {
    public:
        // other code
        const Object* operator -> () const { return this; }
        Object* operator -> () { return this; }
    };
    

    And for your Proxy

    Object operator->() { return container[key]; }
    

    So, for example

    myObj[42]->myFoo = ...
    

    is mostly equivalent to

    Proxy proxy = myObj[42];
    Object obj = proxy.operator ->();
    Object* pobj = obj.operator ->(); // so pobj = &obj;
    pobj->myFoo = ...