3

I am trying to write a templated method that will accept a member function pointer as an argument.

Here is an example class, including my latest attempt to write said template method:

class MyClass
{
public:
   int myMethod() { return 1; }

   template<typename T> 
   T call(std::function<T(MyClass*)> m)
   {
      return m(this);
   }
};

My goal is to be able to do the following (or something very similar in syntax):

MyClass m;
auto result = m.call(&MyClass::myMethod);

So far with my attempt above, I can do:

MyClass m;
std::function<int(MyClass*)> f = &MyClass::myMethod;
auto result = m.call(f);

I was surprised I was not even able to wrap that into a single line. m.call(&MyClass::myMethod) this does not compile. Why?

I am sure there is a way to get the behaviour I want, so any help would be much appreciated!

JeJo
  • 30,635
  • 6
  • 49
  • 88
user1488777
  • 125
  • 4
  • What output are you getting when you run the code? – Dennis Kozevnikoff Jul 06 '20 at 17:18
  • 2
    Something about deducing the type of `T` I think. This, which specifies `T` explicitly, compiles `auto result = m.call([](MyClass* h){return h->myMethod();});` – john Jul 06 '20 at 17:21
  • 1
    When I compile with the syntax I want, I get `candidate template ignored: could not match 'function' against 'int (MyClass::*)()'` I am trying to avoid having to specify return types etc, want as automatic as possible. Technically I can do that, but it's horrible syntax: `m.call(std::function::type(MyClass*)>(&MyClass::myMethod))` – user1488777 Jul 06 '20 at 17:25
  • Why did you pass "this" as argument when you called 'm' in member function template call. i. e m(this) ; – Owl66 Jul 06 '20 at 19:31

2 Answers2

4

Apparently it cannot deduce T (template parameter from a very deduced ctor argument).

If your goal is to simply do it in one call you can change method definition to something like

    template<class F> auto call(F &&f) {
        return std::invoke(std::forward<F>(f), this);
    }
bipll
  • 11,747
  • 1
  • 18
  • 32
3

I was surprised I was not even able to wrap that into a single line. m.call(&MyClass::myMethod) ... this does not compile. Why?

The member function pointer has the following type

<return-type>(ClassType::*)(<args>)<specifiers-if-any>

meaning &MyClass::myMethod has the type

int(MyClass::*)(void)

This is not equal to the type std::function<int(MyClass*)> and the compiler can not directly deduce it, hence the compiler error.

However, if we explicitly mention the template type, it can be deduced to std::function<int(MyClass*)>, with some type erasur overheads. Meaning you could mention the template parameter explicitly like as follows:

/* const */ int result = m.call<int>(&MyClass::myMethod);    // works!
//                             ^^^^^^ -> mention T == int here!

(See Live Demo)


I'm sure there's a way to get the behaviour[...]

If you do not use std::function rather the normal (templated) member function pointer type, this will work.

template<typename T>
T call(T(MyClass::* m)())
{
   return (this->*m)();
   // or
   // std::invoke(m, this); // in C++17
}

Now you could

/* const */ int result = m.call(&MyClass::myMethod);

(See Live Demo)


If the syntax is confusing you could provide a template type alias for the member function pointer like this.

class MyClass 
{
   // template type alias
   template<typename T> using MemFunctionPtrT = T(MyClass::*)();
public:
   // other codes

   template<typename T>
   T call(MemFunctionPtrT<T> m) // use the alias type like
   {
      return (this->*m)();
      // or
      // std::invoke(m, this); // in C++17
   }
};
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 1
    Thanks, this is also a useful explanation of how to do the template function definition correctly to accept the the member function pointer! – user1488777 Jul 06 '20 at 19:22