考虑下面的代码:
#include <iostream>
#include <functional>
struct B {
template <class C, class M, class T>
void call1(C (M::*member)(), T *instance) {
std::function<void()> fp = std::bind(member, instance);
fp();
}
template <class C, class M, class T>
void call2(C (M::*member), T *instance) {
std::function<void()> fp = std::bind(member, instance);
fp();
}
void foo() {
call1(&B::func, this); // works
call2(&B::func, this); // works
call1(&B::func2, this); // Error: no matching member function for call to 'call2'
call2(&B::func2, this); // works
}
void func() {
std::cout << "func\n";
}
void func2() const volatile {
std::cout << "func2\n";
}
};
int main() {
B{}.foo();
}
似乎以后的版本不接受带有额外cv限定符的函数。
像这样指定函数成员指针和像这样指定C (M::*member)
有什么区别
发布于 2016-02-03 22:59:15
让我们简单地考虑一下它们之间的区别:
template <class C, class M> void f(C (M::*member)());
template <class C, class M> void g(C (M::*member));
在f
中,member
是指向M
的成员函数的指针,返回零参数并返回C
。如果你用&B::func
调用它,编译器会推导出M == B
和C == void
。简单明了。
在g
中,member
只是一个指向M
类型为C
的成员的指针。但是,在我们的例子中,&B::func
是一个函数。所以这里的影响就是丢掉指针。我们再次推导出M == B
,而C
变成了void()
-现在C
是一个函数类型。这是f
的一个非专门化版本,因为它允许更多类型的成员。g
可以匹配带参数的函数,或者匹配指向成员的指针,或者相关地匹配cv限定的成员函数。
让我们考虑一个重载函数的例子,以及如何以不同的方式推导它(这是您问题中的原始问题,后来进行了编辑,但仍然很有趣):
struct X {
void bar() { }
void bar(int ) { }
};
当我们这样做的时候:
f(&X::bar);
尽管&X::bar
是一个重载的名称,但实际上只有一个与C (M::*)()
匹配。有M == X
和C == void
的那个。获取int
的bar
重载不可能与模板类型匹配。因此,这是传递重载名称的可接受用法之一。这样可以很好地演绎。
然而,当我们这样做的时候:
g(&X::bar);
现在,有两个完美有效的推论。C
可以是void()
和void(int)
。因为两者都是有效的,所以推导是模棱两可的,而且你无法编译--有一个错误并不能让这一点变得特别清楚。
现在回到你的例子:
call1(&B::func2, this); // Error: no matching member function for call to 'call2'
call2(&B::func2, this); // works
&B::func2
的类型为void (B::*)() const volatile
。由于call1
在不带参数且不是cv限定的成员函数类型上进行推导,因此类型推导就会失败。没有使这些类型匹配的C
或M
。
但是,call2
演绎是很好的,因为它更通用。它可以匹配任何指向成员的指针。我们只是简单地以C = void() const volatile
结束。这就是为什么它可以工作的原因。
https://stackoverflow.com/questions/35179578
复制相似问题