baechlju
baechlju

Reputation: 56

How to extract the arguments of a function pointer as type parameter pack?

I would like to support calls like this

class BaseClass {
public:
  virtual void print2(float arg1, float arg2) = 0;
};

int main() {
  invoke(subclass, &BaseClass::print2, 1, 2);      
}

where

  1. subclass is automatically upcasted to BaseClass
  2. the arguments are automatically converted from int to float
  3. calls with wrong parameter types / class types cause errors

I managed to solve the part with the base class. But the argument part gives me a headache. My approach is to extract all the needed type information from the handed function pointer. The other arguments are casted implicitly then (if possible).

What i have so far is this:

template<class Func> struct FunctionPointer2 {};

template<class Class, typename Arg1, typename Arg2> struct FunctionPointer2<void (Class::*)(Arg1, Arg2)> {
  typedef Class Class;
  typedef Arg1 Argument1;
  typedef Arg2 Argument2;
};

template <typename Func>
void invoke2(typename FunctionPointer2<Func>::Class* obj, Func func, typename FunctionPointer2<Func>::Argument1 arg1, typename FunctionPointer2<Func>::Argument2 arg2) {
  (obj->*func)(arg1, arg2);
}

It works but for each count of parameters another function, function pointer struct, ... is needed.

So here's the question: Is following even possible? How can i make it possible?

template<class Func> struct FunctionPointer {};

template<class Class, typename ... Args> struct FunctionPointer<void (Class::*)(Args ...)> {
  typedef Class Class;
  typedef Args Arguments; // pass type parameter pack here
};

template <typename Func>
void invoke(typename FunctionPointer<Func>::Class* obj, Func func, typename FunctionPointer<Func>::Arguments ... params) {
  (obj->*func)(params ...);
}

Thanks for you help :)

Upvotes: 0

Views: 576

Answers (2)

max66
max66

Reputation: 66200

Not sure to understand... but if you want that the first argument is up-casted... I suppose you're looking something as

template <typename D, typename R, typename B, typename ... As1,
          typename ... As2>
void invoke (D && d, R(B::*pnt)(As1...), As2 && ... args)
 { (dynamic_cast<B&>(std::forward<D>(d)).*pnt)
      (std::forward<As2>(args)...); }

Observe there is a couple of variadic template packs: As1..., for the argument required from the method, As2..., for the args... you pass to invoke.

You can unify this packs as follows

template <typename D, typename R, typename B, typename ... As>
R invoke (D && d, R(B::*pnt)(As...), As && ... args)
void { (dynamic_cast<B&>(std::forward<D>(d)).*pnt)
          (std::forward<As>(args)...); }

but is dangerous because there is the risk that the compiler can't deduce As... types when there isn't a perfect match (as in your example, where you pass two int to a method that wants two float).

The following is a full compiling example

#include <iostream>

struct BaseClass
 { virtual void print2 (float, float) = 0; };

struct Derived : public BaseClass
 {
   void print2 (float f1, float f2)
    { std::cout << f1 << ", " << f2 << std::endl; }
 };


template <typename D, typename R, typename B, typename ... As1,
          typename ... As2>
void invoke (D && d, R(B::*pnt)(As1...), As2 && ... args)
 { (dynamic_cast<B&>(std::forward<D>(d)).*pnt)
      (std::forward<As2>(args)...); }

int main()
 {
   Derived der;

   invoke(der, &BaseClass::print2, 1, 2);      
 }

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96286

Both the manual upcast and extracting the parameter types should be unnecessary.

You can just do this:

template <typename T, typename M, typename ...P>
void invoke(T &&object, M member, P &&... params)
{
    (std::forward<T>(object).*member)(std::forward<P>(params)...);
}

Upvotes: 4

Related Questions