考虑以下代码:
#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)()
这样的函数成员指针和这个C (M::*member)
之间的区别是什么?
答案 0 :(得分: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 - 合格的成员functinos。
让我们考虑一个重载函数的示例以及如何以不同方式推导它(这是您问题中的原始问题,已经编辑过,但仍然很有趣):
struct X {
void bar() { }
void bar(int ) { }
};
当我们这样做时:
f(&X::bar);
即使&X::bar
是重载名称,但只有一个实际匹配C (M::*)()
。有M == X
和C == void
的人。 bar
取代int
的重载与模板类型无关。所以这是传递重载名称的可接受用途之一。这推断了罚款。
然而,当我们这样做时:
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
推断出一个不带args但不是cv-qualified的成员函数类型,所以类型推导就会失败。没有C
或M
来匹配这些类型。
但是,call2
扣除很好,因为它更通用。它可以匹配任何指向成员的指针。我们最终得到的是C = void() const volatile
。这就是为什么这样做的原因。