为啥成员函数不能用作模板参数?

Posted

技术标签:

【中文标题】为啥成员函数不能用作模板参数?【英文标题】:Why member functions can't be used as template arguments?为什么成员函数不能用作模板参数? 【发布时间】:2015-09-04 23:26:33 【问题描述】:

为什么成员函数不能用作模板参数? 例如,我想做这样的事情:

struct Foo 
    void Bar()  // do something
    
;
template <typename TOwner, void(&func)()>
void Call(TOwner *p) 
    p->func();

int main() 
    Foo a;
    Call<Foo, Foo::Bar>(&a);
    return 0;

我知道可以使用指向成员的指针来完成类似的事情; 好吧,大多数时候它已经足够酷了,但我只是好奇为什么“应该”使用指针。

我认为解释上面的“p->func()”没有歧义。 为什么标准禁止我们使用成员函数作为模板参数? 根据我的编译器(VC++ 2013),即使是静态成员函数也是不允许的。有谁知道原因?或者,有没有办法在不因指针取消引用而损失任何性能的情况下做同样的事情?

谢谢。

【问题讨论】:

您看不到任何歧义,因为您认为模板就像宏,但事实并非如此,类型安全是由编译器实现的,您的表达式 p-&gt;func() 在编译时没有意义,而 Foo::Bar 是不是void(&amp;func)() 类型,而是void(TOwner::*func)() @Jean-BaptisteYunès 嗯,Foo::Bar 不是 void(TOwner::*func)() 类型,它是 &Foo::Bar 的类型。我写 void(&func)() 的原因是我能想到的最好的东西是 Foo::Bar; 的类型。实际上,根据我的编译器, decltype(Foo::Bar) 的结果是 void(void) 。我要问的是在允许上述情况时可能会破坏类型安全。谢谢。 可以作为参考。我只是想指出问题在于:类型(参数和返回类型是什么)和签名之间存在一些区别? (上下文/命名空间+类型)。唉 Foo::Bar 签名不是 void(void)。 重点是:Foo::Bar 是一个函数成员,而你的模板 arg 是一个函数,但你将它用作函数成员。 @Jean-BaptisteYunès 是的,我的意思是,不幸的是,既没有“成员函数类型”也没有“对成员类型的引用”,这就是我写上面这样的原因。 【参考方案1】:

它们可以用作非类型参数,但你需要使用正确的语法

struct Foo 
    void Bar()  // do something
    
;
template <typename TOwner, void(TOwner::*func)()>
void Call(TOwner *p) 
    (p->*func)();

int main() 
    Foo a;
    Call<Foo, &Foo::Bar>(&a);
    return 0;

【讨论】:

OP 说过,“我知道可以使用指向成员的指针来完成类似的事情。” @songyuanyao 如果 OP 的意思是“为什么我不能在模板参数中使用对成员的引用”,那么答案是因为没有这样的东西。 C++17 自动模板来不及解决这个问题的调用语法——所以你不需要 中的第一个 Foo > @ganesh 在main 的上下文中没有this 指针,我不明白这个问题。 我们如何更改代码以将成员函数指针作为调用的第二个参数?【参考方案2】:

事实上,成员函数指针可以用作模板参数(就像任何其他指针类型可以用作模板参数一样):

struct A

    int f(float x);
;

template <int (A::F*)(float)>
struct B ;

template<A *> struct C;
template<A &> struct D;

但是,根据 C++ 标准的以下摘录,不能将引用传递给成员。

[温度参数]

    非类型模板参数应具有以下之一 (可选 cv 限定)类型:

(4.1) — 整数或枚举类型,

(4.2) — 指向对象的指针或指向函数的指针,

(4.3) — 对对象的左值引用或对函数的左值引用,

(4.4) — 指向成员的指针,

(4.5) — std::nullptr_t.


接下来,假设你设法以某种方式传递了你的函数类型并想在里面调用它,你会遇到同样的问题,就像你想将它们存储在函数指针或a std::function object中一样:即调用你需要两者,成员函数以及具体对象。只传递函数是不够的。

但实际上你可以实现你想要的。只需将函数绑定到您的对象并在之后传递它:

template<typename T, typename F>
void call(T&& t, F&&f)

    f(std::forward<T>(t));


struct A

    void foo()  std::cout<<"hello"<<std::endl;   
;

int main()

    A a;
    auto f=std::bind(&A::foo, a);   //or possibly "std::ref(a)" instead of "a"
    call(3,f);

DEMO

【讨论】:

对成员的引用不存在。 @user657267:correct,所以你不能将它们用作模板参数:)。说真的,感谢您的关注。 感谢您的友好解释。正如您所说明的那样,我同意 std::bind 是一个近乎完美的解决方案,但我认为这与使用指向成员的指针并没有太大区别;也就是说,如果我这样做,应该添加一些动态开销。当然,具体对象应该绑定到要调用的成员函数,但静态成员并非如此。你怎么看?另外,您不认为“p->”提供了这样的具体对象来绑定吗?我无法想象“将 func() 视为 p 的成员函数”可能会导致问题的情况。 @JunekeyJeon:第一个:是的,p-&gt; 是必需的对象,我在您的代码中忽略了这一点。因此,正如另一个答案所指出的,这“只是”一个语法错误。 @JunekeyJeon:接下来,我认为这两种情况都不涉及“动态开销”,因为编译器都知道它必须调用哪个函数。但是,如果您将其与多态性一起使用,即传递基类指针并调用虚函数(但这是更普遍的事情,与您的问题没有直接关系),则可能会发生开销。关于静态成员:应该可以,是的,但我不直接知道如何......我尽量避免所有这些丑陋的函数指针的东西并使用现代 C++11 替代品。

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

为啥尝试使用显式类型参数调用模板成员函数时会出错?

将 lambda 参数完美转发给成员函数,其中成员函数是非类型模板形参

为啥未使用的成员模板函数的类模板实例化失败

类模板模板类函数模板模板函数

在模板派生类中,为啥我需要在成员函数中使用“this->”来限定基类成员名称?

为啥 PRIVATE 成员函数不能成为另一个类的友元函数?