通用成员函数指针作为模板参数

39

考虑下面的代码:

#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);
}

当然,在第16行编译时,这不会通过,因为编译器不知道RCArgs是什么。但还有另一个问题:如果在ptr_to_mem之前尝试定义这些模板参数,就会遇到这种糟糕的情况:

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);
}

令人惊讶的是,g++没有抱怨Args不是模板列表中的最后一个参数,但无论如何它都不能将proxycall绑定到正确的模板函数,并只是注意到它是一个可能的候选项。

有解决方法吗?我最后的办法是将成员函数指针作为参数传递,但如果我可以将其作为模板参数传递,它会更符合我的代码风格。

编辑:正如一些人所指出的那样,这个例子似乎毫无意义,因为proxycall不会传递任何参数。但在我正在处理的实际代码中,参数是使用一些模板技巧从Lua堆栈中获取的。但是,这部分代码与问题无关,而且相当冗长,所以我不会在这里粘贴它。


在这种情况下,我觉得你实际上不需要变参模板参数。proxycall()不会向成员函数指针调用传递任何参数,因此使用变参模板参数似乎使问题变得比必要的更加困难。 - In silico
1
这没有意义。你的“call_mem_fn”#define实际上没有提供参数。因此,如果Args不为空,它将无法工作。那么你指望这个怎么运行呢? - Nicol Bolas
问题中的代码只是一个示例。实际代码将处理具有任意数量参数的函数,并且它们将从其他地方(即Lua堆栈)检索。提取参数的元编程胶水代码已经工作,由于它很长,我不会在此粘贴。 - Lorenzo Pistone
我试图尝试一下,但是我觉得我在半路上迷失了方向,所以如果答案不正确,请忽略它。 - Kerrek SB
FTR 可变参数包不一定要声明在最后,但这样它只能被推导,而不能由用户使用 <...> 显式指定。 - Luc Danton
2个回答

60
您可以尝试类似这样的方法:
template <typename T, typename R, typename ...Args>
R proxycall(T & obj, R (T::*mf)(Args...), Args &&... args)
{
    return (obj.*mf)(std::forward<Args>(args)...);
}

使用方法:proxycall(obj, &hello::f);

另外,如果要将PTMF变成模板参数,请尝试特化:

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)...);
    }
};

使用方法:

hello obj;

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

// or

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

3
别忘了处理const 成员函数。(但如果你决定不处理 volatile 或者 const volatile,我也不会抱怨。) - aschepler
如何处理常量成员函数? - user1810087
3
const 限定的成员函数指针类型为 R (T::*)(Args...) const - Kerrek SB
1
据我所知,在C++17中,template <typename T, T> struct proxy可以简化为template <auto> struct proxy。至少对我来说是这样的。这极大地简化了使用,特别是对于具有复杂签名的成员函数! - nilo
1
@nilo 感谢您指出 auto,它似乎正好解决了这个问题(https://dev59.com/gVoT5IYBdhLWcg3w6i23#38044251)。否则,如果没有这个特性,我们可以使用 decltype(hello::f) 来消除第一个模板参数的签名复杂度。 - Ad N
显示剩余3条评论

3
在现代C++中,可以使用template<auto>和通用的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{});
}

如果出现函数重载,就应该像注释里的代码一样显式地指定成员函数类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接