Reputation: 401
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
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