0

I am following this code snippet which makes it easier to pass a member function to an interface expecting a C-style callback (that is, the interface expects a function pointer to the callback, and a void* pointer to user data which will in turn be passed to the callback). Effectively I want to convert Helper::M to Helper::V below.

I am trying to modify the snippet to automatically deduce the template parameters. Here is my current attempt.

#include <iostream>

template <typename R, typename T, typename... Args>
struct Helper {
  using V = R (*)(void*, Args...);
  using M = R (T::*)(Args...);
  template <M m>
  static R Fn(void* data, Args... args) {
    return (static_cast<T*>(data)->*m)(std::forward<Args...>(args...));
  }
};

template <typename R, typename T, typename... Args>
typename Helper<R, T, Args...>::V Cast(R (T::*m)(Args...)) {
  return Helper<R, T, Args...>::template Fn<m>;
}

int CIntf(void* data, int (*f)(void*, int)) { return f(data, 1); }

struct UserData {
  int x;
  int Add(int y) { return x + y; }
};

int main(int argv, char** argc) {
  UserData data = {4};
  // Explicit parameters; works.
  std::cout << CIntf(&data, Helper<int, UserData, int>::Fn<&UserData::Add>)
            << "\n";
  // Deduced parameters; fails.
  std::cout << CIntf(&data, Cast(&UserData::Add)) << "\n";

  return 0;
}

I tried to compile with gcc -std=c++11 -lstdc++. The explicit parameters method works fine, but the deduced parameters method gives the following error:

tmp.cc: In instantiation of ‘typename Helper<R, T, Args>::V Cast(R (T::*)(Args ...)) [with R = int; T = UserData; Args = {int}; typename Helper<R, T, Args>::V = int (*)(void*, int)]’:
tmp.cc:30:58:   required from here
tmp.cc:15:42: error: no matches converting function ‘Fn’ to type ‘using V = int (*)(void*, int) {aka int (*)(void*, int)}’
   return Helper<R, T, Args...>::template Fn<m>;
                                          ^~~~~
tmp.cc:8:12: note: candidate is: template<int (UserData::* m)(int)> static R Helper<R, T, Args>::Fn(void*, Args ...) [with R (T::* m)(Args ...) = m; R = int; T = UserData; Args = {int}]
   static R Fn(void* data, Args... args) {

Note that it correctly deduced the template parameters, but failed to convert Helper<int, UserData, int>::Fn<m> to int (*)(void*, int); why? This same conversion succeeded in the explicit case (unless m is somehow different from &UserData::Add).

stewbasic
  • 831
  • 7
  • 21

2 Answers2

1

Unfortunately you'll have to use a macro for this:

#define makeFunc(method) &Helper<decltype(method)>::Fn<method>

And redefine your helper like this for it to work:

template <typename T>
struct Helper;

template <typename R, typename T, typename... Args>
struct Helper<R(T::*)(Args...)>

The reason why you can't use deduction for this, is that deduction only works on function arguments which are run-time values. And you need to use a method's address as template argument which should be a compile-time value.

So when you do this:

return Helper<R, T, Args...>::template Fn<m>;

you are passing a run-time value m as a template argument which is impossible.

r3mus n0x
  • 5,954
  • 1
  • 13
  • 34
  • In other words, when I call `Cast(&UserData::Add)` the compiler must instantiate `Cast` which takes an arbitrary `int(UserData::*)(int)` as argument; it is not sufficient for the compiler to know how to evaluate `Cast(&UserData::Add)` – stewbasic Jul 27 '18 at 06:45
  • @stewbasic, well, yes, since `Cast` can accept any pointer of the appropriate type, you can't use this value as a template argument for `Fn` because this value might not be known at compile time. The compiler error that you are getting about conversion is misleading, the real issue is that it cannot instantiate `Fn` template function using provided template arguments. – r3mus n0x Jul 27 '18 at 07:02
  • @stewbasic, when I compile your example with Clang, it actually gives me a more informative message: `main.cc:8:12: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'm' static R Fn(void* data, Args... args) {`. – r3mus n0x Jul 27 '18 at 07:05
0

For reference, here is the complete code using the macro. Also note the use of std::forward in the original code was incorrect for multiple arguments (see this answer).

#include <iostream>
#include <utility>

template <typename T>
struct Helper;

template <typename R, typename T, typename... Args>
struct Helper<R (T::*)(Args...)> {
  template <R (T::*m)(Args...)>
  static R Fn(void* t, Args... args) {
    return (static_cast<T*>(t)->*m)(std::forward<Args>(args)...);
  }
};

#define VOID_CAST(m) &Helper<decltype(m)>::Fn<m>

struct UserData {
  int x;
  int Add1(int y) { return x + y; }
  int Add2(int y, int z) { return x + y + z; }
};

int Call1(void* data, int (*f)(void*, int)) { return (*f)(data, 1); }
int Call2(void* data, int (*f)(void*, int, int)) { return (*f)(data, 1, 2); }

int main() {
  UserData data = {4};
  std::cout << Call1(&data, VOID_CAST(&UserData::Add1)) << "\n";
  std::cout << Call2(&data, VOID_CAST(&UserData::Add2)) << "\n";
  return 0;
}
stewbasic
  • 831
  • 7
  • 21