泛型成员函数指针作为模板参数

Posted

技术标签:

【中文标题】泛型成员函数指针作为模板参数【英文标题】:generic member function pointer as a template parameter 【发布时间】:2012-04-04 11:10:43 【问题描述】:

考虑这段代码:

#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() 不会将任何参数传递给成员函数指针调用,因此使用可变参数模板参数似乎使问题变得比它需要的更困难。 这没有意义。您的“call_mem_fn”#define 实际上并没有提供参数。因此,如果 Args 不是空的,它将不起作用。那么您如何期望它实际发挥作用呢? 问题中的代码只是一个例子。实际代码将处理具有任意数量参数的函数,并且它们将从其他地方(即 Lua 堆栈)检索。获取参数的元编程胶水代码已经在工作,我不会在这里粘贴它,因为它很长。 我想试一试,但它认为我在中途的某个地方迷路了,所以如果它关闭,请忽略答案。 FTR 可变参数包不必最后声明,但只能推导出来,而不是由&lt;...&gt; 的用户明确指定。 【参考方案1】:

你可以试试这样的:

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, &amp;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);

【讨论】:

我总是忘记,当我遇到函数模板的麻烦时,类模板就是我的救生艇。 别忘了处理const成员函数。 (但如果你决定不打扰volatileconst volatile,我不会抱怨。) @user1810087: const 限定的成员函数指针类型是R (T::*)(Args...) const 据我了解,template &lt;typename T, T&gt; struct proxy 在 C++17 中可以简化为 template &lt;auto&gt; struct proxy。至少对我有用。这极大地简化了使用,特别是对于具有复杂签名的成员函数! @nilo 感谢您指出auto,这似乎是address exactly that。否则,如果没有此功能,您可以使用decltype(hello::f) 来摆脱第一个模板参数的签名复杂性。【参考方案2】:

在现代 C++ 中,可以使用 template&lt;auto&gt; 和通用 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);

如果有重载,应该像注释代码那样明确指定成员函数类型。

【讨论】:

以上是关于泛型成员函数指针作为模板参数的主要内容,如果未能解决你的问题,请参考以下文章

成员函数指针值上的 Consexpr - 未定义的行为?

使用带有模板参数的成员函数指针

std::async 与共享指针模板化成员函数

GCC C++14/17 成员函数指针模板参数的区别

成员函数指针作为构造函数参数

C++函数指针与成员函数指针