TESch
TESch

Reputation: 401

Passing pointer to generic method into template class

I want to make a template class whose constructor takes an object pointer and a pointer to one of the object's methods. The template class must accept a method with any arguments, so I thought the method's type should be a template parameter. I'd also prefer to accept a method with any return type, but it would be OK to constrain it to returning void. The code below doesn't compile. What's the right syntax?

template <typename Obj, typename Method>
class Foo
{
public:
  Foo(Obj *obj, Obj::*Method method)
    :mObj(obj), mMethod(method)
  {}

  void callMethod()
  {
    mObj->mMethod();
  }

private:
  Obj* mObj;
  Obj::*Method mMethod;
};

class Bar
{
public:
  // I want it to work no matter what arguments this method takes.
  void method() {}
};

Bar bar;
Foo <Bar, void(Bar::*)()> foo(&bar, &Bar::method);

I get this error on the Foo constructor:

error C2059: syntax error: '<tag>::*'

My previous question on this topic was marked as duplicate, but the examples cited specify the exact type of method that can be passed, and I need it to be generic.

Upvotes: 0

Views: 568

Answers (1)

cigien
cigien

Reputation: 60208

To start off, you have a couple of syntactic issues in your code. Also, you don't need Method to be a template argument at all, since you always want to pass a member function of Object type.

To get variable number of arguments into the callMethod, just provide a variadic parameter pack to Foo. Also, you can deduce the return type of the callMethod function.

Putting all this together, you can get:

template <typename Obj, typename Ret, typename ...Args>
class Foo
{
public:
  Foo(Obj *obj, Ret (Obj::*method)(Args...))
    : mObj(obj), mMethod(method)
  {}

  Ret callMethod(Args... args)
  {
    return (mObj->*mMethod)(args...);
  }

private:
  Obj* mObj;
  Ret (Obj::*mMethod)(Args...);  // this is essentially 'Method'
};

Now if you have classes with variable number of arguments for a particular function, it works fine:

class Bar
{
public:
  void method() { std::cout << "bar"; }
};

class Car
{
public:
  int method2(int, double) { 
    std::cout << "car"; 
    return 42;
   }
};

int main()
{
Bar bar;
Car car;
Foo a (&bar, &Bar::method);
Foo b (&car, &Car::method2);
a.callMethod();  // prints 'bar'
b.callMethod(5, 3.2);  // prints 'car'
}

Here's a working demo.

Note that in an actual solution, you probably want to perfect-forward the arguments, but this should get you on the right track.

Also, pre-c++17, there is no CTAD (class template argument deduction), so you have to specify the template arguments when constructing Foo objects, like this:

Foo <Bar, void> a(&bar, &Bar::method);
Foo <Car, int, int, double> b(&car, &Car::method2);

Upvotes: 2

Related Questions