Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading operator ->* in C++

I have my own smart pointer implementation and now I am trying to solve the problem of calling the member function by its pointer. I do not provide any get()-like function (actually, I provide an operator->, but I do not want to use it for this purpose).

My question is: what should the signature and the return type of the operator->* look like?

like image 600
svv Avatar asked Dec 24 '14 08:12

svv


2 Answers

For completeness, here's a complete, compilable, minimal example, heavily inspired by this paper I've linked to, and stripped down along with a small usage demo in order to get you started with this:

#include <memory>
#include <iostream>
#include <utility>


// Our example class on which we'll be calling our member function pointer (MFP)
struct Foo {
    int bar() {
        return 1337;
    }
};

// Return value of operator->* that represents a pending member function call
template<typename C, typename MFP>
struct PMFC {
    const std::unique_ptr<C> &ptr;
    MFP pmf;
    PMFC(const std::unique_ptr<C> &pPtr, MFP pPmf) : ptr(pPtr), pmf(pPmf) {}

    // the 'decltype' expression evaluates to the return type of ((C*)->*)pmf
    decltype((std::declval<C &>().*pmf)()) operator()() {
        return (ptr.get()->*pmf)();
    }
};

// The actual operator definition is now trivial
template<typename C, typename MFP>
PMFC<C, MFP> operator->*(const std::unique_ptr<C> &ptr, MFP pmf)
{
    return PMFC<C, MFP>(ptr, pmf);
}

// And here's how you use it
int main()
{
    std::unique_ptr<Foo> pObj(new Foo);
    auto (Foo::*pFn)() = &Foo::bar;
    std::cout << (pObj->*pFn)() << std::endl;
}
like image 69
The Paramagnetic Croissant Avatar answered Oct 30 '22 08:10

The Paramagnetic Croissant


The operator->*() takes two arguments:

  1. The object it is operating on.
  2. The member pointer to be applied.

If the member pointer is just access for a data member, the result is straight forward: you can just return a reference to the member. If it is a function, well, things are a bit more complicated: the member access operator needs to return a callable object instead. The callable object takes the appropriate number of arguments and returns the type of the member pointer.

I realize that the original question is tagged with c++03 but doing a "proper" C++03 implementation is a rather lengthy typing exercise: you'll have to what is done conveniently by variadic templates in the code below for each number of arguments. So, this code uses C++11 primarily to show more clearly what's needed and to avoid running through a typing exercise.

Here is a simple "smart" pointer defining operator->*():

template <typename T>
class my_ptr
{
    T* ptr;
public:
    my_ptr(T* ptr): ptr(ptr) {}

    template <typename R>
    R& operator->*(R T::*mem) { return (this->ptr)->*mem; }

    template <typename R, typename... Args>
    struct callable;
    template <typename R, typename... Args>
    callable<R, Args...> operator->*(R (T::*mem)(Args...));
};

I think for "proper" support it also needs to define const versions: that should be quite straight forward so I'm omitting these. There are basically two version:

  1. One version taking a pointer to a non-function member which just returns the referenced member for the given pointer.
  2. One version taking a pointer to a function member which returns a suitable callable object. The callable will need to have a function call operator and apply it appropriately.

So, the next thing to define is the callable type: it will hold a pointer to the object and a pointer to the member and apply them upon call:

#include <utility>
template <typename T>
     template <typename R, typename... Args>
struct my_ptr<T>::callable {
    T* ptr;
    R (T::*mem)(Args...);
    callable(T* ptr, R (T::*mem)(Args...)): ptr(ptr), mem(mem) {}

    template <typename... A>
    R operator()(A... args) const {
        return (this->ptr->*this->mem)(std::forward<A>(args)...);
    }
};

Well, that's fairly straight forward. The one tricky bit is the fact that arguments the function call operator is being called with may be different types than those the of the pointer to member. The code above deals with the situation by simply forwarding them.

The missing bit is the factory function for the above callable type:

template <typename T>
    template <typename R, typename... Args>
my_ptr<T>::callable<R, Args...> my_ptr<T>::operator->*(R (T::*mem)(Args...)) {
    return callable<R, Args...>(this->ptr, mem);
}

OK, that's all! This is quite a bit of code using fancy C++11 variadic templates. Typing this stuff out to feed it to a C++03 isn't really something I'd fancy. On the plus side, I think these operators can be non-member functions. That is, they could be implemented in a suitable namespace which only contains these operators using and an empty tag-type which a smart pointer type would inherit from. With the tag-tag being a base the operators would be found via ADL and work for all smart pointer. They could, e.g., use operator->() to get hold of the pointer needed to construct the callable.

Using C++11 it is actually reasonably straight forward to implement support for operator->*() independent from any specific smart pointer type. The code below shows the implementation and a simple use. It makes use of the fact that this version can only be found based on ADL (you should never have a using directive or declaration for it) and that smart pointers probably implement operator->(): the code uses this function to get hold of the smart pointer's pointer. The member_access namespace should probably go into a suitable header simply included by other smart pointers which then just inherit from member_access::member_acccess_tag (which can be a private(!) base class as that still triggers ADL to look into member_access).

#include <utility>

namespace member_access
{
    struct member_access_tag {};

    template <typename Ptr, typename R, typename T>
    R& operator->*(Ptr ptr, R T::*mem) {
        return ptr.operator->()->*mem;
    }

    template <typename R, typename T, typename... Args>
    struct callable {
        T* ptr;
        R (T::*mem)(Args...);
        callable(T* ptr, R (T::*mem)(Args...)): ptr(ptr), mem(mem) {}

        template <typename... A>
        R operator()(A... args) const {
            return (this->ptr->*this->mem)(std::forward<A>(args)...);
        }
    };

    template <typename Ptr, typename R, typename T, typename... Args>
    callable<R, T, Args...> operator->*(Ptr ptr, R (T::*mem)(Args...)) {
        return callable<R, T, Args...>(ptr.operator->(), mem);
    }
}

template <typename T>
class my_ptr
    : private member_access::member_access_tag
{
    T* ptr;
public:
    my_ptr(T* ptr): ptr(ptr) {}
    T* operator->() { return this->ptr; }
};
like image 29
Dietmar Kühl Avatar answered Oct 30 '22 09:10

Dietmar Kühl