0

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.

T Scherer
  • 401
  • 7
  • 14

1 Answers1

2

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);
cigien
  • 57,834
  • 11
  • 73
  • 112
  • Your variadic version is equivalent to the other one (`Method` was just `void(int, double)`), but you've lost the ability to handle non-`void` member functions right now. Edit: well, not equivalent, but you got my point: a `ReturnType` parameter would be nice. – Quentin Apr 22 '20 at 15:56
  • Hmm, seems like OP is ok with void return type, but this works either way :) – cigien Apr 22 '20 at 16:01
  • @cigien On my compiler (Visual Studio 2015), it wants template arguments when declaring Foo objects. How are you able to omit them--is that a gcc thing? – T Scherer Apr 22 '20 at 16:08
  • You need c++17. Before that, there's no class-template-argument-deduction. Added fix to answer. – cigien Apr 22 '20 at 16:09
  • @cigien How would this look for C++11? – T Scherer Apr 22 '20 at 16:16
  • See the last part of my answer. That works fine in c++11. – cigien Apr 22 '20 at 16:16