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

19

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

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->func()”没有意义,“Foo::Bar”不是类型为“void(&func)()”,而是类型为“void(TOwner::*func)()”。 - Jean-Baptiste Yunès
1
@Jean-BaptisteYunès 嗯,Foo :: Bar不是void(TOwner :: * func)(&Foo :: Bar的类型)。我写void(&func)()的原因是这是我能想到的最好的Foo :: Bar类型;实际上,根据我的编译器,decltype(Foo :: Bar)的结果是void(void)。我正在询问的是当允许像上面那样的事情时,破坏类型安全性的潜在情况。谢谢。 - Junekey Jeon
好的,针对参考意见我想指出的问题是:类型(参数和返回类型)和签名(上下文/命名空间+类型)之间存在一些区别。不幸的是,Foo::Bar的签名不是void(void)。 - Jean-Baptiste Yunès
重点是: Foo :: Bar是一个函数成员,而你的模板参数是一个函数,但你将其用作函数成员。 - Jean-Baptiste Yunès
@Jean-BaptisteYunès 是的,我的观点是不幸的是既没有“成员函数类型”也没有“成员引用类型”,这就是为什么我写成上面那样的原因。 - Junekey Jeon
2个回答

38

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

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的意思是“为什么我不能在模板参数中使用成员引用”,那么答案是因为这样的东西不存在。 - user657267
C++17的auto模板不能来得更快,以修复此调用语法 - 因此您不需要在<Foo,&Foo :: bar>中使用第一个Foo。 - xaxxon
1
@ganesh,在main的上下文中没有this指针,我不理解这个问题。 - user657267
我们如何更改代码,使成员函数指针成为Call的第二个参数? - TonySalimi
@xaxxon,你能展示一下如何使用自动模板吗? - marczellm

4
事实上,成员函数指针可以用作模板参数(就像任何其他指针类型一样可以用作模板参数):
struct A
{
    int f(float x);
};

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

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

然而,根据C++标准中以下摘录的内容,不能传递成员的引用。
[temp.param] 非类型模板参数必须具有以下(可选的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);
}

{{链接1:演示}}


成员的引用不存在。 - user657267
@user657267: 正确的,所以你不能将它们用作模板参数 :). 真的,感谢您的指出。 - davidhigh
感谢您的详细解释。正如您所阐述的,我认为std::bind是一个几乎完美的解决方案,但我认为这与使用指向成员的指针并没有太大区别;也就是说,如果我这样做,会增加一些动态开销。当然,具体对象应该绑定到要调用的成员函数,但对于静态成员来说,情况并非如此。您对此有何看法?此外,您不认为“p->”提供了这样的具体对象来绑定吗?我无法想象“将func()视为p的成员函数”可能会导致问题的情况。 - Junekey Jeon
@JunekeyJeon:首先,是的,p->是你代码中需要的对象,我在你的代码中忽略了这一点。正如其他答案所指出的那样,这只是一个语法错误。 - davidhigh
@JunekeyJeon:接下来,我认为在这两种情况中都没有涉及到“动态开销”,因为在编译器中都知道需要调用哪个函数。但是如果与多态一起使用,即传递一个基类指针并调用虚函数,可能会发生开销(但这更多是一般性的问题,与您的问题不直接相关)。关于静态成员:应该可以工作,但我不确定具体方法……我尽量避免使用这些丑陋的函数指针方法,并使用现代C++11替代方案。 - davidhigh
@davidhigh 调用虚函数引起的动态开销不是我的关注重点。我认为 std::bind 或函数指针可能会导致动态开销的原因是它们是“指针”。 对于指针的“正常”使用(即不作为模板参数而是作为普通变量),引用它们所指向的地址应产生动态开销。 但是要将其用作模板参数,指针应该是编译时常量,我认为这可能是为什么根本没有开销的原因。 这样对吗? - Junekey Jeon

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