Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 210
P i Avatar asked Dec 29 '14 12:12

P i


People also ask

How does -> operator overload work?

The operator-> has special semantics in the language in that, when overloaded, it reapplies itself to the result. While the rest of the operators are applied only once, operator-> will be applied by the compiler as many times as needed to get to a raw pointer and once more to access the memory referred by that pointer.

Can the -> operator be overloaded?

The class member access operator (->) can be overloaded but it is bit trickier. It is defined to give a class type a "pointer-like" behavior. The operator -> must be a member function. If used, its return type must be a pointer or an object of a class to which you can apply.

Can -> be overloaded in CPP?

You can redefine or overload the function of most built-in operators in C++. These operators can be overloaded globally or on a class-by-class basis. Overloaded operators are implemented as functions and can be member functions or global functions. An overloaded operator is called an operator function.

Which operator can overload?

Operator overloading allows C/C++ operators to have user-defined meanings on user-defined types (classes).


2 Answers

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 = ...
like image 66
Jarod42 Avatar answered Sep 29 '22 13:09

Jarod42


I find the Proxy class that you wrote as an example a bit confusing so i took the liberty to change it a little: Here is a simple example:

//object with lots of members:
class my_obj
{
public:
    std::string     m_text;

    void foo()
    {
        std::cout << m_text << std::endl;
    }
    void bar(std::string t)
    {
        m_text = t;
    }
};
//proxy object
class proxy_class
{
private:
    friend class CustomContainer;
    my_obj* px;

    proxy_class(my_obj * obj_px)
        :px(obj_px)
    {
    }
    proxy_class() = delete;
    proxy_class(const proxy_class &) = delete;
    proxy_class& operator =(const proxy_class &) = delete;
public:

    my_obj* operator ->()
    {
        return px;
    }
};
//custom container that is the only one that can return proxy objects
class CustomContainer
{
public:
    std::map<std::size_t, my_obj> stuff;

    proxy_class     operator [](const std::size_t index)
    {
        return proxy_class(&stuff[index]);
    }
};

example usage:

CustomContainer cc;
cc[0]->foo();
cc[0]->bar("hello world");
cc[0]->foo();

As a design consideration the proxy class should be create in a controlled environment so constructors are removed from preventing miss-usage.

CustomContainer has to only return proxy_class with a reference to my_obj so it can use anything, std::map, std::vector, etc

like image 26
Raxvan Avatar answered Sep 29 '22 15:09

Raxvan