Reputation: 1861
Consider this code:
#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();
}
It seems that the later version don't accept functions with extra cv qualifiers.
What is the difference between specifying a function member pointer like this C (M::*member)()
and like this C (M::*member)
?
Upvotes: 16
Views: 1094
Reputation: 303347
Let's simplify to just considering the difference between:
template <class C, class M> void f(C (M::*member)());
template <class C, class M> void g(C (M::*member));
In f
, member
is a pointer to a member function of M
returning taking zero arguments and returning C
. If you called it with &B::func
, the compiler will deduce M == B
and C == void
. Straightforward.
In g
, member
is just a pointer to a member of M
of type C
. But, in our case, &B::func
is a function. So the impact here is just dropping the pointer. We deduce M == B
again, whereas C
becomes void()
- now C
is a function type. This is a less specialized version of f
in that it allows for more kinds of members. g
can match against functions that take arguments, or against pointers to members, or, relevantly, against cv-qualified member functinos.
Let's consider as an example an overloaded function and how it would get deduced differently (this was the original problem in your question, which has since been edited, but is still interesting):
struct X {
void bar() { }
void bar(int ) { }
};
When we do:
f(&X::bar);
Even though &X::bar
is an overloaded name, only one actually matches C (M::*)()
. The one that has M == X
and C == void
. There is simply no way that the overload of bar
taking an int
matches the template type. So this is one of the acceptable uses of passing an overloaded name. This deduces fine.
However, when we do:
g(&X::bar);
Now, there are two perfectly vaild deductions. C
could be both void()
and void(int)
. Since both are valid, the deduction is ambiguous, and you fail to compile - with an error that doesn't really make this particularly clear.
Now back to your example:
call1(&B::func2, this); // Error: no matching member function for call to 'call2'
call2(&B::func2, this); // works
The type of &B::func2
is void (B::*)() const volatile
. Since call1
deduces on a member function type that takes no args and isn't cv-qualified, the type deduction simply fails. There is no C
or M
to get those types to match.
However, the call2
deduction is fine since it is more generic. It can match any pointer to member. We simply end up with C = void() const volatile
. That's why this works.
Upvotes: 15