38

Consider this code:

#include <iostream>
using namespace std;

class hello{
public:
    void f(){
        cout<<"f"<<endl;
    }
    virtual void ff(){
        cout<<"ff"<<endl;
    }
};

#define call_mem_fn(object, ptr)  ((object).*(ptr))

template<R (C::*ptr_to_mem)(Args...)> void proxycall(C& obj){
    cout<<"hello"<<endl;
    call_mem_fn(obj, ptr_to_mem)();
}

int main(){
    hello obj;
    proxycall<&hello::f>(obj);
}

Of course this won't compile at line 16, because the compiler doesn't know what R, C and Args, are. But there's another problem: if one tries to define those template parameters right before ptr_to_mem, he runs into this bad situation:

template<typename R, typename C, typename... Args, R (C::*ptr_to_mem)(Args...)> 
                             //  ^variadic template, but not as last parameter!
void proxycall(C& obj){
    cout<<"hello"<<endl;
    call_mem_fn(obj, ptr_to_mem)();
}

int main(){
    hello obj;
    proxycall<void, hello, &hello::f>(obj);
}

Surprisingly, g++ does not complain about Args not being the last parameter in the template list, but anyway it cannot bind proxycall to the right template function, and just notes that it's a possible candidate.

Any solution? My last resort is to pass the member function pointer as an argument, but if I could pass it as a template parameter it would fit better with the rest of my code.

EDIT: as some have pointed out, the example seems pointless because proxycall isn't going to pass any argument. This is not true in the actual code I'm working on: the arguments are fetched with some template tricks from a Lua stack. But that part of the code is irrelevant to the question, and rather lengthy, so I won't paste it here.

Lorenzo Pistone
  • 5,028
  • 3
  • 34
  • 65
  • It seems to me that you don't actually need variadic template parameters in this case. `proxycall()` isn't going to pass any arguments to the member function pointer invocation, so using variadic template parameters appears to make the problem more difficult than it needs to be. – In silico Mar 19 '12 at 23:23
  • 1
    This doesn't make sense. Your "call_mem_fn" #define doesn't actually provide parameters. So it won't work if Args is anything but empty. So how do you expect this to actually function? – Nicol Bolas Mar 19 '12 at 23:25
  • The code in the question is just an example. The actual code will handle functions with an arbitrary number of arguments, and they will be retrieved from somewhere else (namely, a Lua stack). The metaprogramming glue code that fetches the arguments is already working, I won't paste it here as it's lengthy. – Lorenzo Pistone Mar 19 '12 at 23:39
  • I was trying to give it a shot, but It think I lost track somewhere half-way through, so please ignore the answer if it's off. – Kerrek SB Mar 19 '12 at 23:45
  • FTR a variadic parameter pack doesn't have to be declared last, but then it can only be deduced, and not explicitly specified by the users with `<...>`. – Luc Danton Mar 20 '12 at 06:55

2 Answers2

60

You could try something like this:

template <typename T, typename R, typename ...Args>
R proxycall(T & obj, R (T::*mf)(Args...), Args &&... args)
{
    return (obj.*mf)(std::forward<Args>(args)...);
}

Usage: proxycall(obj, &hello::f);

Alternatively, to make the PTMF into a template argument, try specialization:

template <typename T, T> struct proxy;

template <typename T, typename R, typename ...Args, R (T::*mf)(Args...)>
struct proxy<R (T::*)(Args...), mf>
{
    static R call(T & obj, Args &&... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
};

Usage:

hello obj;

proxy<void(hello::*)(), &hello::f>::call(obj);

// or

typedef proxy<void(hello::*)(), &hello::f> hello_proxy;
hello_proxy::call(obj);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I always forget that when I'm in troubles with function templates, class templates are my lifeboat. – Lorenzo Pistone Mar 19 '12 at 23:53
  • @LorenzoPistone: Typically you implement your magic with a class template and use specialization, and in the end you add a little type-deducing *function* template so you don't have to spell out the template parameters. In this case, though, I don't really know how this helps, but perhaps this is enough to give you an idea. – Kerrek SB Mar 19 '12 at 23:55
  • 3
    Don't forget to deal with `const` member functions. (But if you decide not to bother with `volatile` or `const volatile`, I wouldn't complain.) – aschepler Mar 20 '12 at 00:23
  • how would it look like to deal with const member functions? – user1810087 Jan 16 '16 at 02:16
  • 3
    @user1810087: The const-qualified member function pointer type is `R (T::*)(Args...) const`. – Kerrek SB Jan 16 '16 at 14:30
  • @Nikos: 1) The second template parameter is a non-type (= value) parameter. Just like in `template struct IntArray { int data[N]; };`. Just in our case the type of the non-type parameter is itself parametrised. 2) Partial specialization, I imagine. – Kerrek SB Sep 22 '19 at 20:36
  • 1
    As far as I understand, `template struct proxy` could be simplified to `template struct proxy` in C++17. That worked for me, at least. That greatly simplifies the usage, especially for member functions with complex signatures! – nilo Jun 13 '21 at 18:54
  • 1
    @nilo Thank you for pointing out `auto`, which seems to [address exactly that](https://stackoverflow.com/a/38044251/1027706). Otherwise, without this feature one coulde use `decltype(hello::f)` to get rid of the signature complexity for first template parameter. – Ad N Jan 04 '22 at 11:03
3

In modern C++ one can use template<auto> and generic lambda-wrapper:

#include <utility>
#include <functional>

template<auto mf, typename T>
auto make_proxy(T && obj)
{
    return [&obj] (auto &&... args) { return (std::forward<T>(obj).*mf)(std::forward<decltype(args)>(args)...); };
}

struct R {};
struct A {};
struct B {};

struct Foo
{
    R f(A &&, const B &) { return {}; }
    //R f(A &&, const B &) const { return {}; }
};

int main()
{
    Foo foo;
    make_proxy<&Foo::f>(foo)(A{}, B{});
    //make_proxy<static_cast<R (Foo::*)(A &&, const B &) const>(&Foo::f)>(std::as_const(foo))(A{}, B{});
    //make_proxy<static_cast<R (Foo::*)(A &&, const B &)>(&Foo::f)>(foo)(A{}, B{});
}

If there are overloadings one should to specify member function type explicitly as in commented code.

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169